Reactで「Can’t perform a React state update on an unmounted component」警告

React を使っていると、コンソールに 「Warning: Can’t perform a React state update on an unmounted component」 という警告が出ることがあります。これはアプリが直ちに壊れるエラーではありませんが、潜在的なメモリリークや意図しない挙動につながるため、放置すべきではありません。

この記事ではこの警告が出る原因と、React Hooks を用いた実践的な解決方法を整理します。

実際に表示される警告メッセージの例は以下のようになります。

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

初心者はこれを「無害な警告だから無視してよい」と誤解しがちですが、アプリが大規模化すると警告が氾濫し、本質的な問題を見逃す原因になるため要注意です。

1. 警告の意味と発生する状況

この警告は「アンマウントされたコンポーネントの state を更新しようとした」ときに出ます。典型的には以下のようなシナリオです。

  • useEffect 内で非同期処理を開始する
  • API フェッチや setTimeout などが終わる前にコンポーネントがアンマウントされる
  • 完了後に setState を呼び出そうとするが、対象が既に存在しない

React は「対象がもう存在しないのに更新しようとしているよ」と警告を出してくれるのです。

例として次のようなコードを考えます。

useEffect(() => {
  fetch("/api/data")
    .then(res => res.json())
    .then(data => setState(data));
}, []);

ここで画面遷移などでコンポーネントが消えた後に fetch が完了すると、この警告が発生します。

2. よくある間違い例

初心者がやりがちな誤解として「この警告は無視してよい」という対応があります。たしかに画面は動作することが多いですが、内部的には不要な更新やリークが発生し続けます。特に大量の非同期処理を扱う場面ではパフォーマンス劣化につながります。

また setTimeout / setInterval を止め忘れることも多く、これもアンマウント後の state 更新を誘発する原因になります。例えば以下のようなコードは典型的な失敗例です。

useEffect(() => {
  const timer = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
}, []);

この場合、コンポーネントがアンマウントされても interval が動き続けるため、警告が発生します。正しくは cleanup で clearInterval(timer) を呼び出す必要があります。

3. 解決策その1: isMounted フラグを使う

最もシンプルな対処は、コンポーネントがまだマウントされているかどうかを判定するフラグを利用する方法です。

useEffect(() => {
  let isMounted = true;

  fetch("/api/data")
    .then(res => res.json())
    .then(data => {
      if (isMounted) {
        setState(data);
      }
    });

  return () => {
    isMounted = false;
  };
}, []);

ここではクリーンアップ関数でフラグを false にし、アンマウント後には setState が実行されないようにしています。これにより不要な警告を防止できます。

4. 解決策その2: AbortController を使う

よりモダンな方法は AbortController を利用してリクエスト自体をキャンセルする方法です。fetch API は AbortController をサポートしており、コンポーネントのライフサイクルに沿った中断処理を実装できます。

useEffect(() => {
  const controller = new AbortController();

  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(data => setState(data))
    .catch(err => {
      if (err.name !== "AbortError") {
        console.error(err);
      }
    });

  return () => controller.abort();
}, []);

これにより不要なリクエストを中止でき、警告を根本的に防ぐことができます。

5. 実運用での注意点

  • 複数の非同期処理を並列で走らせる場合、それぞれに適切なキャンセル処理を入れる必要がある
  • ライブラリによっては内部でキャンセルをサポートしていない場合があるため、Promise チェーンにガード条件を入れることも重要
  • React 18 の Strict Mode では useEffect が二重に呼ばれるため、より顕著に警告が出やすい

また、チーム開発や CI 環境では、警告がログを埋め尽くすこともあり、放置するとデバッグが困難になります。特に SSR や API 呼び出しが多い環境では、警告の管理は運用効率に直結します。

さらに、React Query や SWR といったデータフェッチングライブラリを導入することで、この問題をフレームワークレベルで回避できる場合もあります。これらはアンマウント時のキャンセル処理が内部的に実装されており、個別に isMounted や AbortController を書く手間を減らせます。

6. まとめ

本記事では React で「Can't perform a React state update on an unmounted component」警告が出る原因と解決策を紹介しました。

  • 原因は「アンマウント後の state 更新」
  • isMounted フラグでガードする方法がシンプル
  • AbortController を使うと非同期処理自体を安全にキャンセル可能
  • 実運用では複数リクエストや Strict Mode にも注意が必要
  • ライブラリ利用で負担を軽減することも可能

この警告を正しく扱うことは、堅牢でパフォーマンスの高い React アプリを構築する上で不可欠です。無視せず、設計段階から適切な非同期処理管理を組み込むことが重要です。

参考

タイトルとURLをコピーしました