ReactでuseEffectでマウントを行おうと思った時に2回useEffectが実行されたことはありませんか?
今回はその問題を解決する方法を探そうと思います。
useEffectとは
Reactコンポーネントを外部システムと同期させるためのHooksです。
外部との通信は副作用(サイドエフェクト)と呼ばれます。
Reactは純粋関数で実装することが定説となっています。
そのためコンポーネント関数は、あるprops(入力)に対して、予測可能なJSXが返ってくるようになっています。
しかし、外部のシステム、例えばAPIを叩いてリソースを取得してDOMを構築するとなるとAPIから何が返ってくるか等はReactアプリケーションのコードだけでは予測できません。
そのため、予測できない動作である副作用を引き起こすuseEffect
はあまり使い回さないようにすることが推奨されています。
マウントとは
Reactを使ったブラウザの描画において、必要なコンポーネントのインスタンスとDOMノードの作成、それらをDOMを挿入するプロセスをマウント
と言います。
簡単にいうとコンポーネントの初期化、コンポーネントを最初に描画することになるページの遷移時における描画プロセスのことをマウント
と言います。
Reactのライフサイクルには、マウント、更新、アンマウントの3種類があると言われています。
ライフサイクルの種類 | トリガー |
---|---|
マウント | コンポーネントの初期化 |
更新 | propsやstateの更新 |
アンマウント | コンポーネントの破棄 |
useEffectを行うとこれらをまとめて行うことができるようになります。
useEffectが2回行われる理由
ReactにはStrictModeと呼ばれる機能があります。
StrictModeがオンになっているとマウントが2回行われるようになっています。
これはアプリケーションの潜在的なバグを見つけるために開発環境ではデフォルトでオンになっています。
StrictModeをオフにすることも可能ですが、毎回プロジェクトの立ち上げごとにオフにするのもめんどくさいと思うのでStrictModeがオンの状態でも問題ないコードを書くべきだと思います。
useEffectが2回行われないようにするには
クリーンアップ関数をうまく使うことで2回マウントを実行されることを防ぐことができます。
下のようにすることで一度マウントすることでignoreがtrueになり、一度しか実行できなくなります。
let ignore = false;
useEffect(() => {
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);