🍣

react-hooks/exhaustive-deps無視してない?

2023/03/19に公開

初めに

useEffect を用いて、レンダリング時と同期的に関数を実行させたい時に、依存関係を第二引数に入れないと linter がエラーを吐き出します。

しかし、アプリの動作に問題がない場合は、下記のようにして lint を無視するコードを書いてしまったことはあるのではないでしょうか?
基本的に、リンターは無視せずに、従うようにしましょう

useEffect(() => {
  // ...
  // 🔴 Avoid suppressing the linter like this:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

https://react.dev/learn/removing-effect-dependencies#why-is-suppressing-the-dependency-linter-so-dangerous

では、どのように lint のエラーの解消を行なっていけばよいでしょうか?
以下の質問に答えてみて解決策を探してみてください。

そもそも useEffect 使わないとだめ?

最初に考えるべきことは、このコードが useEffect であるべきかどうかです。
イベントハンドラーでもいいのではないか?ということを検討しましょう。
Effect はユーザーの操作とは関係ない箇所で起こることですので、フォームを送信した時などのユーザー操作ではイベントハンドラーを使うようにしましょう。

useEffect 内で無関係なことをしていないか?

無関係なロジックをエフェクトに追加しないでください。
コード内の各効果は、別個の独立した同期プロセスを表す必要があります。

function ChatRoom({ roomId }) {
  useEffect(() => {
    logVisit(roomId);
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
  // ...
}

たとえば、ユーザーが部屋を訪れたときに分析イベントを送信するとします。 roomId に依存する useEffect が既にあるので、そこに分析呼び出しを追加したくなるかもしれません。
しかし、後で接続を再確立する必要がある別の依存関係をこの Effect に追加することを想像してください。その時に、logVisit 関数も無関係ですが、呼び出されてしまいます。
訪問の記録は、接続とは別のプロセスです。それらを 2 つの個別のエフェクトとして記述します。

function ChatRoom({ roomId }) {
  useEffect(() => {
    logVisit(roomId);
  }, [roomId]);

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    // ...
  }, [roomId]);
  // ...
}

状態を計算するために状態を読みっ取ってはいないか?

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    connection.on('message', (receivedMessage) => {
      setMessages([...messages, receivedMessage]);
    });
    return () => connection.disconnect();
  }, [roomId, messages]); // ✅ All dependencies declared
  // ...

message を受けとって新しい配列を作るために、message を読みって計算しています。
メッセージを受信するたびに、 setMessages() により、受信したメッセージを含む新しいメッセージ配列でコンポーネントが再レンダリングされます。
ただし、このエフェクトはメッセージに依存するようになったため、エフェクトも再同期されます。そのため、新しいメッセージがあるたびに、チャットが再接続されます。ユーザーはそれを望まないでしょう!
下記のような形で、message state を使用しなくても記述することができます。不用意な useEffect の実行を防ぐことができます。

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    connection.on('message', (receivedMessage) => {
      setMessages(msgs => [...msgs, receivedMessage]);
    });
    return () => connection.disconnect();
  }, [roomId]); // ✅ All dependencies declared
  // ...

最後に

下記で凡例を示してくれてます。

https://react.dev/learn/removing-effect-dependencies#challenges

GitHubで編集を提案

Discussion