How to fetch data with React Hooksを読む
これね
【読了】
この記事で学んだこと
- 非同期処理とReactのHooks:
- データフェッチと状態設定:
- コンポーネントのアンマウント問題:
useReducer
と状態管理:- カスタムフックの作成:
- コードのリファクタリングと改善:
データフェッチするためのコードがとても参考になったのと、ここに至るまでのコード変遷のおかげで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>
);
}
非同期関数はイベントループを介して非同期に動作する関数のため欲しいデータ結果を返却せずにPromiseを返却する。そのため、上記のようにクリーンアップ関数として定義することで、useEffctは何も返却しないor クリーンアップ関数より取得したオブジェクトを返却する必要がある。
という認識だったがChatGPTに聞いたら誤りでした(恥ずかし)
誤解している内容 | 正しい内容 |
---|---|
非同期関数をuseEffect 内で直接呼び出せる |
非同期関数をuseEffect 内で直接呼び出すことはできません |
useEffect はクリーンアップ関数を返す必要がある |
useEffect はクリーンアップ関数を返すことができ、これはオプショナルです |
非同期関数はデータを直接返す | 非同期関数はPromiseを返し、それが解決されるとデータが利用可能になります |
という点で誤解があり、
非同期関数はイベントループを通じて非同期に動作し、実際のデータを直接返すのではなくPromiseを返します。useEffect
内で非同期関数を直接呼び出すことはできず、代わりに非同期関数を定義し、その関数をuseEffect
内で呼び出すことができます。そして、useEffect
は必要に応じてクリーンアップ関数を返すことができます。
また、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>
);
}
はーなるほど。
CodeSandBoxで動くものを作成
おー動くぞ。楽しい。
useReducerが出てきたあたりから難しくなってきたな。。
あれそうでもないか?複数の状態管理が適切に行えるようになるなら便利なのでは
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;
-
dispatch
:dispatch
関数は、アクションオブジェクトを受け取り、そのアクションオブジェクトをレデューサー関数に渡して状態を更新します。 - アクションオブジェクト: アクションオブジェクトは通常
type
プロパティを持ち、任意でpayload
プロパティを持つことができます。-
type
: アクションの種類を示す文字列です。これにより、レデューサーはどのアクションがディスパッチされたのかを識別し、状態をどのように更新するかを判断します。 -
payload
: アクションに関連する任意のデータを含むプロパティです。payload
はオプショナルで、アクションタイプだけで十分な情報を提供できる場合や、追加のデータが不要な場合には省略されることがあります。
-
もう少しだけ詳しく
dispatch
とpayload
は、状態管理のコンテキストでよく使用される用語です。それらの概念を理解することは、useReducer
やReduxなどの状態管理ライブラリを使いこなす上で重要です。
dispatch:
dispatch
は、アクションを送信する関数です。この関数を呼び出すことで、アクションオブジェクトをreducer関数に送信し、状態を変更することができます。dispatch
関数は、useReducer
フックから返されるもので、通常は以下のように使用されます。
dispatch({ type: 'ACTION_TYPE', payload: someData });
payload:
payload
は、アクションオブジェクトのプロパティの一部で、アクションに関連する任意のデータを保持します。payload
はオプショナルであり、その内容はアプリケーションの要件に応じて異なります。通常、payload
はreducer関数に送信され、新しい状態の計算に使用されます。
例えば、データをフェッチしてそれを状態に保存するアクションを考えてみましょう。このアクションのtype
はFETCH_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プロパティにセットされます
};
このように、dispatch
とpayload
を使用することで、アクションを通じて状態を変更し、必要なデータをreducer関数に渡すことができます。これらの概念は、状態管理の流れと、アクションがどのように状態の変更を引き起こすのかを理解する上で非常に重要です。
"Reactにおける非同期データ取得時のアンマウント問題とその対処法
課題:
Reactでは、コンポーネントがアンマウントされた(画面から削除された)後にそのコンポーネントの状態を更新しようとすると問題が生じる可能性があります。特に、非同期データ取得が完了し、その結果をコンポーネントの状態に設定しようとするとき、もしコンポーネントがすでにアンマウントされていた場合、エラーが発生する可能性があります。この問題は、React Routerを使用してアプリケーション間でナビゲートする際に特に発生しやすいです。
対策:
コンポーネントがアンマウントされたことをトラッキングし、アンマウントされた後に状態設定関数を呼び出さないようにすることで、この問題を回避できます。具体的には、useEffect フックのクリーンアップ関数を使用して、コンポーネントがアンマウントされたときに非同期操作をキャンセルまたは無効にすることができます。クリーンアップ関数は、コンポーネントがアンマウントされた時に実行されるため、これにより非同期データ取得の完了を待って状態を設定する操作を安全にキャンセルまたは無効にすることができます。
以下まとめ
1. 非同期処理とReactのHooks:
-
useEffect
と非同期処理の基本を学びました。 - 非同期関数は
useEffect
内で直接呼び出すことはできず、代わりに非同期関数を定義し、その関数をuseEffect
内で呼び出すことができることを理解しました。
2. データフェッチと状態設定:
-
useState
とuseEffect
を使用してデータをフェッチし、そのデータをコンポーネントの状態に設定する方法を学びました。
3. コンポーネントのアンマウント問題:
- コンポーネントがアンマウントされた後に状態を設定しようとすると問題が生じること、そしてその問題を回遍する方法について学びました。
useReducer
と状態管理:
4. -
useReducer
の基本的な使用法を学び、dispatch
とaction
オブジェクトの概念を理解しました。 -
type
とpayload
プロパティの重要性と使用方法を理解しました。
5. カスタムフックの作成:
-
useDataApi
カスタムフックを作成し、非同期データフェッチのロジックを整理しました。
6. コードのリファクタリングと改善:
- コードを段階的にリファクタリングし、改善し、最終的には
useReducer
を使用して状態管理のロジックをより構造化しました。