はじめに
Reactでデータフェッチを行うとき、useEffect
とasync/await
を組み合わせるのはよくあるパターンです。
しかし、次のように書いたことはありませんか?
React.useEffect(async () => {
const data = await fetchData({ query, page, tag });
setResults(data.results);
}, [query, page, tag]);
実はこれ、非推奨の書き方です。
なぜ useEffect(async () => {}) がダメなのか?
useEffect
に渡す関数は、Reactの仕様上、
-
undefined
を返す - または「クリーンアップ関数」を返す必要があります。
ところが、async
関数は常に Promise を返すため、Reactが期待している戻り値の形式に合いません。
その結果…
- クリーンアップが正しく動作しない
- 将来のReactのアップデートで警告やバグの原因になる
といった問題が起こる可能性があります。
✅ 正しい書き方:async 関数を定義する
React.useEffect(() => {
const handleFetchData = async () => {
setLoading(true);
const data = await fetchData({ query, page, tag });
setResults(data.results);
setLoading(false);
};
handleFetchData();
}, [query, page, tag]);
このように、非同期関数は useEffect の中で定義して実行するのが基本です。
async関数を useEffect の外に出すべき?
一見、handleFetchData
を外に出したほうが「きれい」になりそうに見えます。でも、それには注意が必要です。
❌ 原則:依存変数がある場合は外に出さないほうが良い
const handleFetchData = async () => {
const data = await fetchData({ query, page, tag }); // ❌ query などが stale になる可能性
setResults(data.results);
};
useEffect(() => {
handleFetchData(); // ❌ 依存配列だけ更新されても中身が古いまま
}, [query, page, tag]);
このコードは、handleFetchData
が query
, page
, tag
をクロージャとしてキャプチャしてしまうため、値が古くなる可能性があります。
✅ 例外:再利用したいときは useCallback + 依存配列で管理
たとえば「再取得」ボタンを用意したい場合:
const handleFetchData = useCallback(async () => {
setLoading(true);
const data = await fetchData({ query, page, tag });
setResults(data.results);
setLoading(false);
}, [query, page, tag]);
useEffect(() => {
handleFetchData();
}, [handleFetchData]);
return <button onClick={handleFetchData}>再取得</button>;
これは、
handleFetchData が変わったときだけ、呼び出す
という意味の useEffect
です。分解してみると:
「依存の値 (
query
,page
,tag
) が変わった → 関数 (handleFetchData
) が変わった → 副作用 (useEffect
) 実行」という意図した挙動を実現
-
handleFetchData()
は API を呼ぶなどの副作用(=副作用関数)です。 -
useEffect
の依存配列[handleFetchData]
に注目。- これは「
handleFetchData
の中身が変わったときに再実行する」設定です。 - 中身が変わるというのは、
useCallback
で依存が変わって再生成されたときのこと。
- これは「
メリット
-
useCallback
を使えば関数の再生成を防げる -
query
,page
,tag
を依存に入れることで常に最新値が使われる
まとめ
-
useEffect
には直接async
関数を渡さない! -
handleFetchData
は中で定義 oruseCallback
で管理 - Reactの再レンダーや依存関係を理解したうえで、関数のスコープ設計を考えるのが大事
シチュエーション | 推奨されるパターン |
---|---|
useEffect内だけで使う |
useEffect の中で定義 |
他のイベントでも使いたい |
useCallback でメモ化して外に出す |
依存がない / グローバル関数 | 外に出してもOK |