Reactで非同期データフェッチを賢く使う!useReducer フックとuseEffect フックのベストプラクティス

2024-06-08

React useReducer で非同期データフェッチを行う

React の useReducer フックと非同期処理を組み合わせることで、API からデータをフェッチし、アプリケーションの状態を更新することができます。この方法は、useState フックよりも複雑な状態管理に適しています。

手順

  1. useReducer フックを使用する:
    • ステートとアクションディスパッチャを初期化する
  2. reducer 関数を作成する:
  3. 非同期処理を行うアクションを作成する:
    • useEffect フックまたはカスタムフックを使用して、API フェッチを実行する
    • フェッチが成功したら、アクションディスパッチャを使用してステートを更新する
  4. コンポーネント内でステートとアクションを使用する:
    • ステートをレンダリングし、アクションを dispatch する

import React, { useReducer } from 'react';

const initialState = {
  loading: true,
  data: null,
  error: null,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true };
    case 'FETCH_SUCCESS':
      return { loading: false, data: action.payload, error: null };
    case 'FETCH_ERROR':
      return { loading: false, data: null, error: action.payload };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    dispatch({ type: 'FETCH_START' });

    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then((response) => response.json())
      .then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
      .catch((error) => dispatch({ type: 'FETCH_ERROR', payload: error }));
  }, []);

  if (state.loading) {
    return <div>Loading...</div>;
  }

  if (state.error) {
    return <div>Error: {state.error.message}</div>;
  }

  return <div>{state.data.title}</div>;
};

export default App;

補足

  • 上記の例では、useEffect フックを使用して非同期処理を行っています。
  • カスタムフックを使用して非同期処理をカプセル化することもできます。
  • useReducer フックは、複雑な状態管理をシンプルかつ効率的に行うのに役立ちます。



サンプルコードの詳細解説

useReducer フックの初期化

const initialState = {
  loading: true,
  data: null,
  error: null,
};

const [state, dispatch] = useReducer(reducer, initialState);
  • initialState オブジェクトは、ステートの初期値を定義します。
    • loading: データの読み込み中かどうかを示すフラグ
    • data: API から取得したデータ
    • error: エラーが発生した場合はその内容
  • useReducer フックを使用して、reducer 関数と initialState を元にステートとアクションディスパッチャを初期化します。

reducer 関数

const reducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true };
    case 'FETCH_SUCCESS':
      return { loading: false, data: action.payload, error: null };
    case 'FETCH_ERROR':
      return { loading: false, data: null, error: action.payload };
    default:
      return state;
  }
};
  • reducer 関数は、アクションの種類に応じてステートを更新するロジックを記述します。
    • FETCH_START: データの読み込みを開始したときに呼び出され、loading フラグを true に設定します。
    • FETCH_SUCCESS: データの読み込みが成功したときに呼び出され、loading フラグを false に設定し、data プロパティに取得したデータを格納します。
    • default: アクションの種類が不明な場合は、現在のステートをそのまま返します。

非同期処理を行うアクションの作成

useEffect(() => {
  dispatch({ type: 'FETCH_START' });

  fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then((response) => response.json())
    .then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
    .catch((error) => dispatch({ type: 'FETCH_ERROR', payload: error }));
}, []);
  • useEffect フックを使用して、非同期処理を実行します。
    • dispatch({ type: 'FETCH_START' }): データの読み込みを開始するアクションを dispatch します。
    • fetch('https://jsonplaceholder.typicode.com/todos/1'): 指定されたURLからAPIをフェッチします。
    • then((response) => response.json()): フェッチが成功した場合は、レスポンスをJSON形式に変換します。
    • then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data })): データの読み込みが成功した場合は、FETCH_SUCCESS アクションを dispatch し、payload プロパティに取得したデータを格納します。
  • [] を空配列として渡すことで、コンポーネントのマウント時に一度だけ実行されます。

コンポーネント内でステートとアクションを使用する

if (state.loading) {
  return <div>Loading...</div>;
}

if (state.error) {
  return <div>Error: {state.error.message}</div>;
}

return <div>{state.data.title}</div>;
  • ステートの loading プロパティが true の場合は、"Loading..." というメッセージを表示します。
  • ステートの error プロパティが null ではない場合は、エラーメッセージを表示します。
  • それ以外の場合は、ステートの data プロパティの title プロパティの値を表示します。
  • 上記の例では、https://jsonplaceholder.typicode.com/todos/1 という架空のAPIを使用しています。実際のアプリケーションでは、使用するAPIに合わせてURLを変更する必要があります。
  • エラー処理をより詳細に行う場合は、error プロパティにエラーコードや詳細なメッセージなどを格納



React で非同期データフェッチを行うその他の方法

useState フックと useEffect フック

  • 最もシンプルで分かりやすい方法です。
  • 小規模なアプリケーションや単純なデータフェッチに適しています。
import React, { useState, useEffect } from 'react';

const App = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => setError(error));
  }, []);

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (!data) {
    return <div>Loading...</div>;
  }

  return <div>{data.title}</div>;
};

export default App;

カスタムフック

  • 複雑な非同期処理をカプセル化したい場合に適しています。
  • コードの再利用性と保守性を向上させることができます。
import React, { useState } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => setError(error));
  }, [url]);

  return { data, error };
};

const App = () => {
  const { data, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (!data) {
    return <div>Loading...</div>;
  }

  return <div>{data.title}</div>;
};

export default App;

サードパーティライブラリ

  • react-reduxMobX などのライブラリを使用すると、より複雑な状態管理を行うことができます。

サスペンス

  • React 18 で導入された新しい機能です。
  • データが読み込まれるまでコンポーネントをブロックすることができます。
import React, { useState, suspense } from 'react';

const App = () => {
  const [data, setData] = useState(null);

  const fetchData = async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const data = await response.json();
    setData(data);
  };

  return (
    <React.Suspense>
      {fetchData()}
      {data ? <div>{data.title}</div> : null}
    </React.Suspense>
  );
};

export default App;

それぞれの方法のメリットとデメリット

方法メリットデメリット
useState フックと useEffect フックシンプルで分かりやすい複雑な状態管理には不向き
カスタムフック複雑な非同期処理をカプセル化できるコード量が増える
サードパーティライブラリ複雑な状態管理が可能学習コストが高い
サスペンスデータの読み込み状況を明確に表現できるReact 18 以降でしか利用できない

どの方法を使用するかは、アプリケーションの要件や開発者の好みによって異なります。

  • シンプルで分かりやすい方法を求めている場合は、useState フックと useEffect フックを使用するのがおすすめです。
  • 複雑な非同期処理をカプセル化したい場合は、カスタムフックを使用するのがおすすめです。
  • 大規模なアプリケーションや複雑なデータフローを持つ場合は、サードパーティライブラリを使用するのがおすすめです。
  • 最新の機能を利用したい場合は、サスペンスを使用するのがおすすめです。

reactjs react-hooks


ReactJS SyntheticEvent stopPropagation() 関数:詳細解説

ReactJS の SyntheticEvent オブジェクトには、stopPropagation() 関数という便利なメソッドが用意されています。この関数は、イベントバブリングを制御するために使用されます。イベントバブリングとは、イベントが DOM ツリーを伝播していく現象のことを指します。...


Reactでモバイルとデスクトップを賢く判別!ユーザー体験をワンランクアップさせる方法

window. innerWidth と window. innerHeight プロパティは、ブラウザウィンドウの幅と高さをピクセル単位で返すことができます。これらのプロパティを使用して、デバイスのサイズを大まかに判断することができます。例えば、以下のようなコードを使用できます。...


JavaScript、ReactJS、React Routerにおける「No restricted globals」プログラミング

この制限は、コードの安全性、信頼性、保守性を向上させるために役立ちます。グローバル変数や関数は、コード全体でアクセス可能なので、誤って使用されると予期しない動作を引き起こす可能性があります。「No restricted globals」を使用することで、以下のような問題を防ぐことができます。...


[TypeScript 入門] React でステートを操る:初心者でも安心のガイド

ステートは、コンポーネントのデータ属性であり、時間経過とともに変化します。例えば、ボタンクリックでカウント数を増減するような機能では、カウント数がステートとして管理されます。React では、useState フックを使用してステートを管理します。このフックは、ステート変数とその更新用関数を含むタプルを返します。...


eslint: no-case-declarationエラーを回避して、ReactJSとReduxのコードをより保守性の高いものにする

このエラーは、switch文のcaseブロック内で変数を宣言しようとした際に発生します。ReactJSとReduxでは、switch文を使用してコンポーネントの状態やアクションの種類に基づいて処理を分岐させることがよくあります。このエラーは、コードの読みやすさや保守性を低下させる可能性があるため、修正する必要があります。...