はじめに
React Hooksを使用する際、条件付きでHooksを呼び出したくなることがあります。
しかし、これは重大なアンチパターンであり、エラーや予期せぬ動作を引き起こす可能性があり、本記事では、そのアンチパターンの例と理由、そして最適な解決策をTypeScript(TSX)のコード例とともに解説します。
アンチパターン例:条件付きでHooksを呼び出す
import React, { useState, useEffect } from 'react';
interface ExampleComponentProps {
shouldFetch: boolean;
}
const ExampleComponent: React.FC<ExampleComponentProps> = ({ shouldFetch }) => {
const [data, setData] = useState<string | null>(null);
// 条件付きでuseEffectを呼び出す(アンチパターン)
if (shouldFetch) {
useEffect(() => {
fetchData().then((response) => setData(response));
}, []);
}
return (
<div>
{data ? <p>{data}</p> : <p>Loading...</p>}
</div>
);
};
export default ExampleComponent;
なぜこれが問題なのか
React Hooksは、コンポーネントのレンダーごとに同じ順序で呼び出される必要があり、条件文やループ内でHooksを呼び出すと、レンダーごとに呼び出し順序が変わる可能性があります。
これにより、Reactが内部で管理しているフックの状態と実際の呼び出しが不一致となり、以下のようなエラーが発生します。
Error: React has detected a change in the order of Hooks called...
このエラーは、Hooksのルール違反を検知したReactが投げるものです。
レンダーとは
Reactコンポーネントが描画(表示)される際のプロセス、つまりコンポーネント関数が実行される一連の流れを指す。
・プロパティ(props)の変更
親コンポーネントから渡されるデータが変化したとき。
・状態(state)の変更
useStateなどで管理しているコンポーネント内の状態が変化したとき。
・コンテキスト(context)の変更
useContextで参照しているコンテキストの値が変化したとき。
最適な解決策:Hooks内で条件分岐を行う
条件に応じて処理を分岐させたい場合は、Hooks自体は常に呼び出し、その内部で条件分岐を行うようにします。
import React, { useState, useEffect } from 'react';
interface ExampleComponentProps {
shouldFetch: boolean;
}
const ExampleComponent: React.FC<ExampleComponentProps> = ({ shouldFetch }) => {
const [data, setData] = useState<string | null>(null);
// useEffectは常に呼び出す
useEffect(() => {
if (shouldFetch) {
fetchData().then((response) => setData(response));
}
}, [shouldFetch]); // 依存配列にshouldFetchを追加
return (
<div>
{data ? <p>{data}</p> : <p>Loading...</p>}
</div>
);
};
export default ExampleComponent;
解説
useEffectの呼び出しは常に行う
これにより、Hooksの呼び出し順序が一定になります。
内部で条件分岐を行う
shouldFetchの値に応じて、fetchDataを呼び出すかどうかを制御します。
依存配列にshouldFetchを追加
shouldFetchが変化したときに効果が再実行されるようにします。
補足:fetchData関数の実装例
以下は、fetchData関数の簡単な実装例です。
const fetchData = async (): Promise<string> => {
// APIからデータを取得する処理を記述
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data.message; // 例として、取得したデータのmessageプロパティを返す
};
まとめ
Hooksは常に同じ順序で呼び出す:条件文やループ内でHooksを呼び出してはいけません。
条件分岐はHooksの内部で行う:必要に応じて処理を制御しますが、Hooks自体の呼び出しは固定します。
依存配列を正しく設定する:Hooks内で使用する変数やプロップスを依存配列に含めます。
公式ドキュメントを参照する:ReactのHooksのルールを再確認しましょう。
これらのポイントを押さえることで、Hooksを正しく活用し、安定したReactコンポーネントを作成することができます。
さいごに
Reactの基礎的なことですが、開発する際不意にやってしまうので、自分への再勉強としてこれから複数のアンチパターンでまとめたいと思います。
参考資料
Reactアンチパターン集
【 Reactのアンチパターン 】1, ( React Hooks) 条件付きでHooksを呼び出すアンチパターンとその解決策