ReactuseEffectフックは、バージョン16.8で導入されて以来、Reactライブラリの人気機能です。関数コンポーネントの内部でデータの取得、DOMの更新、イベントの購読などの副作用(サイドエフェクト)を実行することができます。

しかし、useEffectフック使用時には、時として問題が生じることも。例えば、「React Hook useEffect has a missing dependency. Either include it or remove the dependency array.」(ReactのuseEffectフックに依存関係がありません。これをインクルードするか、依存関係の配列を削除してください)は、開発者が遭遇する一般的なエラーメッセージです。

今回はこのエラーメッセージを紐解き、解決する方法をいくつかご紹介します。

「React Hook useEffect has a missing dependency」エラーの原因

「React Hook useEffect has a missing dependency」エラーは、useEffectフックの依存配列が不完全、または見つからない場合に表示されます。

依存配列はuseEffectフックの第2引数で、エフェクトが依存する変数を指定するために使用します。つまり、依存関係の配列で指定された変数の値が変更されると、エフェクトが再び実行されることを意味します。

エフェクトが依存する変数が依存配列に含まれていない場合、値が変更されてもエフェクトが再実行されないことがあります。これはアプリケーションの予期しない動作やバグにつながる可能性があります。

このエラーはReactのエラーではなく、ESLintのエラーです。ESLintにはReact専用のプラグインがあり、Reactコード記述を支援するルールが搭載されています。ルールの1つにある"react-hooks/exhaustive-deps"ルールが、「React Hook useEffect has a missing dependency」エラーを検出します。

例として、カウントの状態を持つ関数コンポーネントを見てみます。このコンポーネントはcountの値が変更されるたびに、コンソールにメッセージを出力します。

import { useState, useEffect } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`${count}回クリックしました`);
  }, []);

  return (
    <div>
      <h1>Hello World</h1>
      <p>回数:{count}</p>
      <button onClick={() => setCount(count + 1)}>1増やす</button>
    </div>
  );
};

export default App;

上の例には、useStateuseEffectフックを使用する関数コンポーネントがあります。useEffectフックは、状態変数countの値が変更されるたびに、その値を含むメッセージをログに記録するのに使用されます。

しかし、useEffectフックの第2引数の配列(依存配列)に、count変数が含まれていません。これが、「React Hook useEffect has a missing dependency」エラーを引き起こします。

「React Hook useEffect has a missing dependency」エラーメッセージ
「React Hook useEffect has a missing dependency」エラーメッセージ

「React Hook useEffect has a missing dependency」エラーを解決する(3つの方法)

このエラーの解決方法はいくつかあります。それぞれ詳しくご紹介していきます。

  • 足りない依存関係を追加する
  • オブジェクトや関数を扱う際にはメモ化された値を返すフックを使用する
  • ESLintルールを無効にする

1. useEffect依存配列に足りない依存関係を追加する

このエラーを解決する手っ取り早い方法は、useEffectフックで使用されているすべての依存関係を依存関係の配列に追加することです。

依存関係を特定するには、useEffectフックの内部で使用されている変数や値を確認します。これらの変数や値が時間の経過とともに変化する可能性がある場合は、依存関係の配列に加えなければなりません。

例えば、先に示したコードでは、count変数がuseEffectフックの内部で使用されていますが、依存配列には含まれていません。そのため、count変数が変更された場合、useEffectフックが再び実行されず、コンポーネントが古い状態のデータやその他の問題を抱える可能性があります。

このエラーを解決するには、count変数を依存配列に追加してください。

import { useState, useEffect } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`${count}回クリックしました`);
  }, [count]);

  return (
    <div>
      <h1>Hello World</h1>
      <p>回数:{count}</p>
      <button onClick={() => setCount(count + 1)}>1増やす</button>
    </div>
  );
};

export default App;

依存配列にcount変数を追加することで、この値が変更されるたびに、useEffectフックを再実行するようReactに指示できるようになります。

これにより、コンポーネントが常に最新のデータを持つようになり、「React Hook useEffect has a missing dependency」エラーを回避できます。

依存関係が複数ある場合は、カンマで区切って追加します。

import { useState, useEffect } from 'react';
const App = () => {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);

  const handleFirstNameChange = (event) => {
    setFirstName(event.target.value);
  };

  const handleLastNameChange = (event) => {
    setLastName(event.target.value);
  };

  return (
    <div>
      <label>
        名:
        <input type="text" value={firstName} onChange={handleFirstNameChange} />
      </label>
      <label>
        姓:
        <input type="text" value={lastName} onChange={handleLastNameChange} />
      </label>
      <p>フルネーム:{fullName}</p>
    </div>
  );
};

export default App;

2. オブジェクトと関数の処理

オブジェクトや配列を扱う場合、依存配列に追加するだけでは不十分で、不要な再レンダリングを避けるためメモ化するか、useEffectフックに移動するか、コンポーネントの外に移動しなければなりません。

JavaScriptでは、オブジェクトや配列は参照によって比較され、毎回メモリ上の異なる場所を指すため、レンダリングごとに値が変わり、無限の再レンダリングループが発生してしまいます。

このエラーを引き起こす例を見てみましょう。

import { useState, useEffect } from 'react';

const App = () => {
  const [user, setUser] = useState({});

  // 👇️ これがレンダリングごとに変化
  let newUser = { name: 'Jane', age: 28 };

  useEffect(() => {
    setUser(newUser);
  }, [newUser]);

  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
};

export default App;

オブジェクトをuseEffectフックに移動するか、コンポーネントの外に移動することで解決可能です。

import { useState, useEffect } from 'react';

const App = () => {
  const [user, setUser] = useState({});

  useEffect(() => {
    let newUser = { name: 'Jane', age: 28 };
    setUser(newUser);
  }, []);

  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
};

export default App;

より良い解決策として、useMemoのようなメモ化された値を返すフックをオブジェクトに使用し、useCallbackを関数に使用することもできます。これにより、オブジェクトや関数をコンポーネント内や依存配列内に保持することができます。

補足)メモ化のフックとは、高価な計算結果をキャッシュし、不必要な再計算を避けるのに便利なフックです。

useMemoフックを使ってオブジェクトをメモ化すると、以下のようなコードになります。

import { useState, useEffect, useMemo } from 'react';

const App = () => {
  const [user, setUser] = useState({});

  const newUser = useMemo(() => {
    return { name: 'John', age: 30 };
  }, []);

  useEffect(() => {
    setUser(newUser);
  }, [newUser]);

  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
};

export default App;

同じように、関数を扱う際には、useCallbackフックを使用することができます。

3. ESLintのルールを無効にする

先にも触れたとおり、「React Hook useEffect has a missing dependency」エラーはESLintの警告エラーです。この方法はすべての状況で推奨されるわけではありませんが、依存関係の欠落が問題ではないことが明らかである場合は、この方法が有効になります。

依存関係配列の行の前に、以下のコメントを追加してください。

// eslint-disable-next-line react-hooks/exhaustive-deps

例を見てみます。

useEffect(() => {
  console.log(`You clicked ${count} times`);
  
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

なお、ESLintルールを無効すると、不注意で別の問題を引き起こす可能性があるためご注意ください。ルールを無効にする前にこの点を考慮し、可能であれば他2つの解決策を実行してみてください。

まとめ

「React Hook useEffect has a missing dependency」エラーは、useEffectフックを使用する際にReact開発者が直面しがちな問題です。

エラーを解決する際は、実際の状況に適した解決策を講じることが重要です。基本的にはESLintルールを無効にすることは極力避けるのが賢明です。足りない依存関係を依存配列に追加するか、適切なメモ化のフックを使用して、解決してみてください。

このエラーに遭遇した経験はありますか?今回ご紹介したもの以外にも解決策をご存知の場合は、コメント欄でお聞かせください。