Skip to content

ReactのuseEffectで「Uncaught TypeError: destroy is not a function」エラーを解決する

エラー発生の概要

ReactアプリケーションでuseEffectフックを使用中に"Uncaught TypeError: destroy is not a function"エラーが発生し、ページ遷移後に空白画面が表示されるケースがあります。特に認証後のリダイレクト処理で頻発し、手動リロードで復旧する特徴があります。

問題の根本原因

useEffectフックが非関数値を返すと発生します。 ReactはuseEffectの返り値をクリーンアップ関数として扱うため、関数以外(nullundefined、Promiseオブジェクト等)を返すとこのエラーが発生します。

js
useEffect(() => {
  if (shouldRedirect) {
    navigate('/home');
    return null; // 非関数値を返している
  }
}, [dependencies]);

主な解決策

1. 不要な返り値を削除する(推奨)

クリーンアップが必要ない場合、何も返さないのが最もシンプルな解決策です。

js
useEffect(() => {
  if (auth && user?.emailVerified) {
    navigate('/app/overview');
  }
}, [auth, user?.emailVerified, user?.dstoreName]);

2. 空のクリーンアップ関数を返す

条件付きでクリーンアップが必要な場合、ダミー関数を返します。

js
useEffect(() => {
  if (auth && user.emailVerified) {
    navigate(`/app/overview`);
    return () => {}; // 空関数を返す
  }
}, [auth, user]);

3. 非同期処理の実装方法を修正(async/await使用時)

useEffectコールバック直にasyncを付けず、内部で非同期関数を呼び出します。

js
useEffect(async () => {  // 非推奨
  const data = await fetchData();
  setState(data);
}, []);
js
useEffect(() => {
  const fetchDataAsync = async () => {
    const data = await fetchData();
    setState(data);
  };

  fetchDataAsync();
}, []);

4. 括弧{}がないことによる誤動作の修正

アロー関数で{}を省略すると意図せず値を返すことがあります。

js
useEffect(() => 
  condition ? doActionA() : doActionB() 
, []);
js
useEffect(() => {
  condition ? doActionA() : doActionB();
}, []);

依存配列に関する重要な注意点

元コードの// eslint-disable-next-lineは深刻なバグの原因になり得ます。

js
useEffect(() => {
  // ...
  // eslint-disable-next-line react-hooks/exhaustive-deps ❌
}, [auth, user]); // 依存配列が不十分

ベストプラクティス

依存配列は実際に使用されている値で完全に構成します。オブジェクトの特定プロパティのみ必要な場合、オブジェクト全体ではなく個別プロパティを依存関係に設定します。

js
useEffect(() => {
  if (auth && user.dstoreName) {
    // ...
  }
}, [auth, user.dstoreName]); // 変更を監視すべきプロパティのみ

補足:リリースビルドでの挙動

useEffect内のnavigateによるリダイレクトが開発環境では動作しても、本番ビルドで問題が発生することがあります。これはReactのStrict Modeが開発時にコンポーネントを二重マウントするためで、クリーンアップ関数の実装が特に重要であることを示しています。

js
useEffect(() => {
  if (!auth) return;

  const redirectTimer = setTimeout(() => {
    navigate('/secure-area');
  }, 100);

  return () => clearTimeout(redirectTimer); // クリーンアップでタイマー解除
}, [auth]);

重大な誤解

useEffectの代わりにuseStateを使う」という提案は誤りです。状態管理と副作用処理は目的が全く異なり、代替不能です。

まとめ

  • useEffectから返す値は必ず関数にする(または何も返さない)
  • 非同期処理は内部関数でラップして実行
  • 依存配列は実際に使用する値のみを列挙
  • ESLint警告(exhaustive-deps)を原則無効化しない

これらのプラクティスを遵守することで、エラーを予防しReactアプリケーションの信頼性を大幅に向上させられます。