Closed13

How to fetch data with React Hooksを読む

MAAAAAAAAAAAMAAAAAAAAAAA

データフェッチするためのコードがとても参考になったのと、ここに至るまでのコード変遷のおかげでuseEffectの再レンダリングに関する知見が深まった。

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}
MAAAAAAAAAAAMAAAAAAAAAAA

非同期関数はイベントループを介して非同期に動作する関数のため欲しいデータ結果を返却せずにPromiseを返却する。そのため、上記のようにクリーンアップ関数として定義することで、useEffctは何も返却しないor クリーンアップ関数より取得したオブジェクトを返却する必要がある。

という認識だったがChatGPTに聞いたら誤りでした(恥ずかし)

誤解している内容 正しい内容
非同期関数をuseEffect内で直接呼び出せる 非同期関数をuseEffect内で直接呼び出すことはできません
useEffectはクリーンアップ関数を返す必要がある useEffectはクリーンアップ関数を返すことができ、これはオプショナルです
非同期関数はデータを直接返す 非同期関数はPromiseを返し、それが解決されるとデータが利用可能になります

という点で誤解があり、

非同期関数はイベントループを通じて非同期に動作し、実際のデータを直接返すのではなくPromiseを返します。useEffect内で非同期関数を直接呼び出すことはできず、代わりに非同期関数を定義し、その関数をuseEffect内で呼び出すことができます。そして、useEffectは必要に応じてクリーンアップ関数を返すことができます。

また、useEffectのクリーンアップ関数は非同期操作の結果を返す目的で使用されるものではありません。クリーンアップ関数は、コンポーネントのアンマウントや再レンダリング前に実行されることを目的としており、特定の副作用をキャンセルするために使用されます。例えば、イベントリスナーを削除する場合やタイマーをクリアする場合に利用されます。非同期操作の結果を扱うには、非同期関数内で状態を設定し、それに応じてコンポーネントを再レンダリングすることが通常の方法です。

MAAAAAAAAAAAMAAAAAAAAAAA
function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

はーなるほど。

MAAAAAAAAAAAMAAAAAAAAAAA

useReducerが出てきたあたりから難しくなってきたな。。

MAAAAAAAAAAAMAAAAAAAAAAA

あれそうでもないか?複数の状態管理が適切に行えるようになるなら便利なのでは

MAAAAAAAAAAAMAAAAAAAAAAA
import React, { Fragment, useState, useEffect, useReducer } from "react";
import axios from "axios";

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_INIT":
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case "FETCH_SUCCESS":
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload
      };
    case "FETCH_FAILURE":
      return {
        ...state,
        isLoading: false,
        isError: true
      };
    default:
      throw new Error();
  }
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: "FETCH_INIT" });

      try {
        const result = await axios(url);

        if (!didCancel) {
          dispatch({ type: "FETCH_SUCCESS", payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: "FETCH_FAILURE" });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};

function App() {
  const [query, setQuery] = useState("redux");
  const [
    { data, isLoading, isError },
    doFetch
  ] = useDataApi("https://hn.algolia.com/api/v1/search?query=redux", {
    hits: []
  });

  return (
    <Fragment>
      <form
        onSubmit={(event) => {
          doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={(event) => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map((item) => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

MAAAAAAAAAAAMAAAAAAAAAAA
  1. dispatch: dispatch関数は、アクションオブジェクトを受け取り、そのアクションオブジェクトをレデューサー関数に渡して状態を更新します。
  2. アクションオブジェクト: アクションオブジェクトは通常typeプロパティを持ち、任意でpayloadプロパティを持つことができます。
    • type: アクションの種類を示す文字列です。これにより、レデューサーはどのアクションがディスパッチされたのかを識別し、状態をどのように更新するかを判断します。
    • payload: アクションに関連する任意のデータを含むプロパティです。payloadはオプショナルで、アクションタイプだけで十分な情報を提供できる場合や、追加のデータが不要な場合には省略されることがあります。
MAAAAAAAAAAAMAAAAAAAAAAA

もう少しだけ詳しく


dispatchpayloadは、状態管理のコンテキストでよく使用される用語です。それらの概念を理解することは、useReducerやReduxなどの状態管理ライブラリを使いこなす上で重要です。

dispatch:

dispatchは、アクションを送信する関数です。この関数を呼び出すことで、アクションオブジェクトをreducer関数に送信し、状態を変更することができます。dispatch関数は、useReducerフックから返されるもので、通常は以下のように使用されます。

dispatch({ type: 'ACTION_TYPE', payload: someData });

payload:

payloadは、アクションオブジェクトのプロパティの一部で、アクションに関連する任意のデータを保持します。payloadはオプショナルであり、その内容はアプリケーションの要件に応じて異なります。通常、payloadはreducer関数に送信され、新しい状態の計算に使用されます。

例えば、データをフェッチしてそれを状態に保存するアクションを考えてみましょう。このアクションのtypeFETCH_SUCCESSで、payloadはフェッチされたデータを含んでいます。

{
  type: 'FETCH_SUCCESS',
  payload: fetchedData
}

このアクションオブジェクトは、dispatch関数を使用してreducerに送信されます。

dispatch({ type: 'FETCH_SUCCESS', payload: fetchedData });

そして、reducer関数内で、payloadのデータは新しい状態を作成するために使用されます。

case 'FETCH_SUCCESS':
  return {
    ...state,
    isLoading: false,
    isError: false,
    data: action.payload,  // payloadのデータが新しい状態のdataプロパティにセットされます
  };

このように、dispatchpayloadを使用することで、アクションを通じて状態を変更し、必要なデータをreducer関数に渡すことができます。これらの概念は、状態管理の流れと、アクションがどのように状態の変更を引き起こすのかを理解する上で非常に重要です。

MAAAAAAAAAAAMAAAAAAAAAAA

"Reactにおける非同期データ取得時のアンマウント問題とその対処法

課題:
Reactでは、コンポーネントがアンマウントされた(画面から削除された)後にそのコンポーネントの状態を更新しようとすると問題が生じる可能性があります。特に、非同期データ取得が完了し、その結果をコンポーネントの状態に設定しようとするとき、もしコンポーネントがすでにアンマウントされていた場合、エラーが発生する可能性があります。この問題は、React Routerを使用してアプリケーション間でナビゲートする際に特に発生しやすいです。

対策:
コンポーネントがアンマウントされたことをトラッキングし、アンマウントされた後に状態設定関数を呼び出さないようにすることで、この問題を回避できます。具体的には、useEffect フックのクリーンアップ関数を使用して、コンポーネントがアンマウントされたときに非同期操作をキャンセルまたは無効にすることができます。クリーンアップ関数は、コンポーネントがアンマウントされた時に実行されるため、これにより非同期データ取得の完了を待って状態を設定する操作を安全にキャンセルまたは無効にすることができます。

MAAAAAAAAAAAMAAAAAAAAAAA

以下まとめ

1. 非同期処理とReactのHooks:

  • useEffectと非同期処理の基本を学びました。
  • 非同期関数はuseEffect内で直接呼び出すことはできず、代わりに非同期関数を定義し、その関数をuseEffect内で呼び出すことができることを理解しました。

2. データフェッチと状態設定:

  • useStateuseEffectを使用してデータをフェッチし、そのデータをコンポーネントの状態に設定する方法を学びました。

3. コンポーネントのアンマウント問題:

  • コンポーネントがアンマウントされた後に状態を設定しようとすると問題が生じること、そしてその問題を回遍する方法について学びました。

4. useReducerと状態管理:

  • useReducerの基本的な使用法を学び、dispatchactionオブジェクトの概念を理解しました。
  • typepayloadプロパティの重要性と使用方法を理解しました。

5. カスタムフックの作成:

  • useDataApiカスタムフックを作成し、非同期データフェッチのロジックを整理しました。

6. コードのリファクタリングと改善:

  • コードを段階的にリファクタリングし、改善し、最終的にはuseReducerを使用して状態管理のロジックをより構造化しました。
このスクラップは2023/11/03にクローズされました