はじめに
Reactでif文の内側でuseEffect
のようなReact Hooksを使おうとした際にエラーが発生しました。
何度か同様のハマりが発生したので備忘録として残しておきます。
該当のコードと発生したエラー
想定した実装としては、useEffect
内部でsetState
した結果を用いて別の処理をしたい、レンダリング後に該当のstateに値が入っていれば別の処理を実行させたいというものでした。
const [solutionText, setSolutionText] = useState<string>("");
const [ploblem, setPloblem] = useState<string>("");
// 処理①:DBで取得したデータをsetStateで変数に格納
useEffect(() => {
const fetchProblem = async () => {
const data = await fetchProblemData();
// 取得するテキストの形式は【課題:(テキスト)】の形式であるとする
setSolutionText(data.text);
};
fetchProblem();
}, []);
// 処理②:レンダリング時にsolutionTextの値が空でなければ後の処理を実行
if (solutionText != "") {
useEffect(() => {
const name = solutionText.match(/【課題:(.+?)】/)![1];
setPloblem(name);
},[]);
}
上記のようなコードを実行したところ、
- React has detected a change in the order of Hooks called by Result. This will lead to bugs and errors if not fixed.
For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks - Uncaught Error: Rendered more hooks than during the previous render.
といった2つのエラーが発生しました。
1つずつ事象と原因を見てみます。
「React has detected a change〜」 エラーについて
該当の英文を翻訳すると以下の内容でした。
Reactは、Resultによって呼び出されるフックの順序に変更が検出されました。この問題を修正しない場合、バグやエラーが発生する可能性があります。
エラー文内で表示されていたURLも見てみましょう。
上記の公式ドキュメントの中でReact Hooks
に関するルールが定義されており、
ループ、条件、またはネストされた関数内ではフックを呼び出してはいけないことが明示的に表記されています。
具体的には以下のケースではHooksは使えないと明記されています。
- 条件式やループの内側でHooksを呼び出すこと
-
return
の後でHooksを呼び出すこと - イベントハンドラでHooksを呼び出すこと
- classコンポーネントの内側でHooksを呼び出すこと
-
useMemo
、useReducer
、useEffect
で渡される関数の内側でHooksを呼び出すこと
これらのケースではHooks以外で同じ挙動を実現できないかの検討が必要となります。
『Rendered more hooks than during the previous render』エラーについて
直訳すると「前回のレンダリング時よりも多くのHooksが生成されました」となります。
つまり想定より多くHooksが生成・実行されてしまっているということのようです。
原因について下記の資料を参考に調べたところ、該当のエラーが発生しうる条件は上記の「具体例」の5パターンでした。
回避策の例
今回想定していたように「特定の条件下でuseEffectした処理を実行したい」「特定の変数のレンダリングに対応して処理を走らせたい」場合、以下のコードで問題を回避できました。
// 処理②の修正後
useEffect(() => {
const renameData = async () => {
if (solutionText != "" && solutionText != null) {
const name = solutionText.match(/【課題:(.+?)】/)![1];
await setPloblem(name);
}
};
renameData();
}, [solutionText]);
ポイントとしては条件式(if文)をuseEffect
の内側に書くこと、useEffectの第二引数に捕捉したい変数を入れることです。
これらを行うことで、前処理のsetState
で変化した値に応じたHookの処理を行うことができます。
条件式やループ等の内側でHooksを使いたくなってしまうケースがあるかもしれませんが、その際はこの記事が参考になれば幸いです。
参考文献