0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js チュートリアル 第11章にてReactフックをネストされた関数定義内部に書いたらエラーになった

Posted at

概要

Next.jsのチュートリアル11章で一覧画面の文字列検索絞り込み機能の実装があった。
検索欄に何かしらの文字列が入力されると絞り込みを走らせる実装になっていたが、処理の負荷軽減のためにデバウンスの実装を体験した。
簡単にデバウンスを体験するために「use-debounce」というライブラリのuseDebouncedCallbackという関数を利用することになった。
しかし、筆者がちゃんと理解してなかったのでuseDebouncedCallbackの記述場所を間違えてエラーが出た。解消含め学びが多かったのでまとめておく。

エラーになったコード

nextjs-dashboard/app/ui/search.tsx
'use client';

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';

export default function Search({ placeholder }: { placeholder: string }) {
  const searchParams = useSearchParams(); // READONLYのURLSearchParamsのインスタンスがreturn
  const pathname = usePathname();
  const { replace } = useRouter();

  function handleSearch(term: string) {
    const handleSearch = useDebouncedCallback((term) => {
      console.log(`Searching... ${term}`);
      const params = new URLSearchParams(searchParams);  // READONLYだと新規検索文字列をURLSearchParamsのインスタンスのプロパティにいれることができないのでコピーを作成
      if (term) {
        params.set('query', term);
      } else {
        params.delete('query');
      }
      replace(`${pathname}?${params.toString()}`);
    }, 300);
  }

  return (
    // JSX
  );
}

ブラウザのconsoleに出たエラー

  • エラーそのまま
react-dom-client.development.js:7843 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.
    at handleSearch (search.tsx:25:46)
    at onChange (search.tsx:46:11)

  • エラー和訳
react-dom-client.development.js:7843 キャッチされないエラー: フック呼び出しが無効です。フックは関数コンポーネントの本体内でのみ呼び出すことができます。これは、次のいずれかの理由で発生する可能性があります。
1. React とレンダラー(React DOM など)のバージョンが一致していない可能性があります。
2. フックのルールに違反している可能性があります。
3. 同じアプリ内に複数の React のコピーがある可能性があります。
この問題のデバッグと修正方法については、https://react.dev/link/invalid-hook-call をご覧ください。

handleSearch (search.tsx:25:46)
onChange (search.tsx:46:11)

正しいコード

nextjs-dashboard/app/ui/search.tsx
'use client';

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';

export default function Search({ placeholder }: { placeholder: string }) {
  const searchParams = useSearchParams(); // READONLYのURLSearchParamsのインスタンスがreturn
  const pathname = usePathname();
  const { replace } = useRouter();

  const handleSearch = useDebouncedCallback((term) => {
    console.log(`Searching... ${term}`);
    const params = new URLSearchParams(searchParams);  // READONLYだと新規検索文字列をURLSearchParamsのインスタンスのプロパティにいれることができないのでコピーを作成
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }, 300);

  return (
    // JSX
  );
}

エラーの原因

今回のエラーの原因は「2. フックのルールに違反している可能性があります。」だった。
どうやらReactフックはコンポーネントのTOPでのみ使うことが許されており、ネストされた関数(今回でいうとhandleSearch)の中での使用はルール違反に当たるらしい。
そのためhandleSearchの中の処理をコンポーネントのTOPで定義・定数格納しJSXにて呼び出すように変更した。
ちなみにuseから始まる関数はすべてReactフックと思って良いらしい。

なぜReactフックはネストされた関数内部で使えないのか

Reactフックは順序に依存してるらしい。
明確に「1番目のフック、2番目のフック、3番目のフック」というようにフック処理を並べて管理しているっぽい。
この順番がレンダリング時と再レンダリング時で異なったりするとエラーになる。(条件分岐によって2番目のフックが呼ばれなかったりすると、レンダリング時:1番目フック → 2番目フック → 3番目フックとなるが、再レンダリング時:1番目フック → 3番目フックとなってしまう)
なのでそもそも条件によってフック呼び出し順が変わるようなコードはエラーになる。
しかしながら今回のコードはただネストされた関数のなかにフックの呼び出しが書いてあるだけでフックの順番は変わらないように思える。ただ、フックの順番が変わるのはなにも条件分岐だけではなく、ネストされた関数がユーザーアクションのイベント駆動で呼ばれるときなども挙げられる。(他にも順番が変わるケースはいくつか存在する。)
これを「イベント駆動のネスト関数内部はフック呼び出し禁止、同期的に実行されるネスト関数内部はフック呼び出し可能」としてしまうと複雑性が上がってしまうため厳格に「ネスト関数内部はフック呼び出し禁止」というルールがあるらしい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?