🍋

useReducerを使ってデータ一覧を表示する

2023/06/21に公開

はじめに

useReducerを使ってデータ一覧を表示する実装をしてみました。

useReducerとは、状態管理に使うことができるReact Hooksの一つ。
状態に応じた処理を管理できます。

公式ドキュメント:
https://react.dev/reference/react/useReducer

開発環境

  • react: 18.2.0

完成イメージ

データ一覧を取得して表示

完成イメージのソースコードはこちらです。

GoalsIndexReducer.ts
import { REQUEST_STATE } from "./GoalsIndexContext";

// 初期状態を定義
export const initialState = {
  fetchState: REQUEST_STATE.INITIAL,
  goalsList: [],
};

// リクエストのアクション状態を定義
export const goalsActionTypes = {
  FETCHING: "FETCHING",
  FETCH_SUCCESS: "FETCH_SUCCESS",
};

// Reducerを定義
export const GoalsIndexReducer = (state, action) => {
  switch (action.type) {
    case goalsActionTypes.FETCHING:
      return {
        ...state,
        fetchState: REQUEST_STATE.LOADING,
      };
    case goalsActionTypes.FETCH_SUCCESS:
      return {
        fetchState: REQUEST_STATE.OK,
        goalsList: action.payload.goals,
      };

    default:
      throw new Error();
  }
};
Goals.tsx
import React, { useEffect, useReducer } from "react";

// apis
import { displayGoals } from "../apis/goals";

import { Box, Card, Flex } from "@chakra-ui/react";

// reducers
import {
  initialState,
  goalsActionTypes,
  GoalsIndexReducer,
} from "../state/GoalsIndexReducer";
import TodaysGoalsList from "./organisms/lists/TodaysGoalsList";

export const Goals = () => {
  const [state, dispatch] = useReducer(GoalsIndexReducer, initialState);
  useEffect(() => {
    dispatch({ type: goalsActionTypes.FETCHING });
    displayGoals().then((data) =>
      dispatch({
        type: goalsActionTypes.FETCH_SUCCESS,
        payload: { goals: data.goals },
      })
    );
  }, []);

  return (
    <Flex align="center" justify="center" direction="column">
      <Box bg="blue" margin="0" w="4xl">
        <Card margin="30px" padding="40px" bg="green">
          {state.goalsList.map((goal) => (
            <TodaysGoalsList
              key={goal.id}
              goal={goal.goal}
              reward_memo={goal.reward_memo}
              reward_icon={goal.reward_icon_id_id}
              achievement_icon={goal.achievement_icon_id_id}
              timestamp={goal.created_at}
            />
          ))}
        </Card>
      </Box>
    </Flex>
  );
};

export default Goals;

状態管理のイメージ

ユーザーがインプットする(URLアクセス)

アクション(一覧取得)

dispatchでReducerに状態(state)を通知

アクションと状態に応じて、Reducerが処理を実行する

※useReducerを使うメリット
・状態管理のロジックを共通化できるので、複数コンポーネントで使いたいときに便利
・useStateと違う点:複数のstateを一定のロジックにしたがってまとめて管理することができる

実装

■Reducerの作成

(1)Stateの初期状態を定義します

// 初期状態を定義
export const initialState = {
  fetchState: REQUEST_STATE.INITIAL,
  goalsList: [],
};

fetchStateではAPIの状態を定義しています。
一覧取得の際の初期値は空配列です。

(2)リクエストのアクションを定義します

// リクエストのアクションを定義
export const goalsActionTypes = {
  FETCHING: "FETCHING",
  FETCH_SUCCESS: "FETCH_SUCCESS",
};

(3)Reducer関数を定義します

上記で設定したアクションの中身によって処理を分岐します。
API取得が成功したら、一覧データを返します。

// Reducerを定義
export const GoalsIndexReducer = (state, action) => {
  switch (action.type) {
    case goalsActionTypes.FETCHING:
      return {
        ...state,
        fetchState: REQUEST_STATE.LOADING,
      };
    case goalsActionTypes.FETCH_SUCCESS:
      return {
        fetchState: REQUEST_STATE.OK,
        goalsList: action.payload.goals,
      };

    default:
      throw new Error();
  }
};

■画面に表示

一覧画面を表示したいコンポーネント上でuseReducerを使って、
先程作成したReducerを呼び出します。

export const Goals = () => {
  const [state, dispatch] = useReducer(GoalsIndexReducer, initialState);
  useEffect(() => {
    dispatch({ type: goalsActionTypes.FETCHING });
    displayGoals().then((data) =>
      dispatch({
        type: goalsActionTypes.FETCH_SUCCESS,
        payload: { goals: data.goals },
      })
    );
  }, []);

  return (
    <Flex align="center" justify="center" direction="column">
      <Box bg="blue" margin="0" w="4xl">
        <Card margin="30px" padding="40px" bg="green">
          {state.goalsList.map((goal) => (
            <TodaysGoalsList
              key={goal.id}
              goal={goal.goal}
              reward_memo={goal.reward_memo}
              reward_icon={goal.reward_icon_id_id}
              achievement_icon={goal.achievement_icon_id_id}
              timestamp={goal.created_at}
            />
          ))}
        </Card>
      </Box>
    </Flex>
  );
};

useReducer()に対して、第一引数にReducer、第二引数にinitialState(初期状態)を渡しています。
そして、その返り値を[state, dispatch]というかたちで受け取っています。
stateは状態、dispatchはアクションに該当します。
この時点のstateは初期状態です。

その後、displayGoals関数で一覧データの取得を行います。
データの取得が成功した後、dispatch関数を使ってReducerに通知します。
その際、
・アクションのtype(goalsActionTypes.FETCH_SUCCESS=データ取得成功)
・payload:取得したデータの内容
をReducerに渡しています。

Reducerはこれを受け取って処理を行い、stateを更新します。
そして、stateに格納されたデータを一覧に表示させます。

以上で実装完了です!

Discussion