かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React 条件分岐のある箇所で Hooks を使うとエラーになる

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement. というエラーに出会ったのでメモ。

環境
  • React ^17.0.1

エラーが発生した経緯

ロード中と完了後に別のコンポーネントを表示したいとか、状態が違う時に別のコンポーネントを表示したいとか JSX 内に分岐がある箇所に Hooks を使うとエラーになるっぽい。

エラーの再現ができなかったのですが、map でループして表示させているコンポーネントを条件分岐させて、片方で props を整形させる hooks を使っていると上記のエラーになりました。

const Articles: VFC<ArticlesProps> = (props) => {
  const [isEdit, setIsEdit] = useState(false);
  const onChangeMode = useCallback((mode: boolean) => () => {
    setIsEdit(mode);
  }, []);
  
  return isEdit
    ? <ArticleEditForm onEditEnd={onChangeMode(false)} {...props} />
    : <ArticleBody {...useBuildArticleProps(props)} onEdit={onChangeMode(true)}>;
};

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <ul>{data.map((item) => <Articles {...item} />)}</ul>}
    </div>
  );
};

=> Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

条件によって Hooks の数が変わってしまう事が原因なようで、useBuildArticleProps を条件式で上の JSX ではなく、呼び出したコンポーネント内に移動させればエラーにはならなくなりました。

// 別のコンポーネントにしてその中から Hooks  を呼び出せばOK
const ArticleContainer: VFV<ArticleContainerProps> = ({onEdit, ...props}) => {
  return <ArticleBody {...useBuildArticleProps(props)} onEdit={onChangeMode(true)}>;
};

const Articles: VFC<ArticlesProps> = (props) => {
  const [isEdit, setIsEdit] = useState(false);
  const onChangeMode = useCallback((mode: boolean) => () => {
    setIsEdit(mode);
  }, []);
  
  return isEdit
    ? <ArticleEditForm onEditEnd={onChangeMode(false)} {...props} />
    : <ArticleContainer onEdit={onChangeMode(true)} {…props}>;
};

ESLint で eslint-plugin-react-hooks を使っていると、条件付きで Hooks を呼び出す箇所をエラーにしてくれる

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <Articles {…useBuildProps(data)} />}
    </div>
  );
};

=> React Hook "useBuildProps" is called conditionally. React Hooks must be called in the exact same order in every component render
※ ESLint で eslint-plugin-react-hooks を導入してない場合このエラーは表示されない

useBuildProps を条件分岐で呼び出した別のコンポーネント内で実行するようにすればOK

const ArticleContainer: VFC<ArticleContainerProps> = ({ data }) => {
  return <Article {…useBuildProps(data)} />;
};

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <ArticleContainer data=(data) />}
    </div>
  );
};

条件分岐したコンポーネントで Hooks が呼ばれてるから同じなのでは?と感じてしまうのですが React 的には別のコンポーネント内に閉じていると問題ないっぽい。

所管

ESLint の eslint-plugin-react-hooks プラグインを使っていれば、条件分岐の JSX 内で Hooks を呼び出しているとエラーを出してくれるので事前に回避することができそうです。


[参考]

はじめてつくるNext.jsサイト

はじめてつくるNext.jsサイト

実践TypeScript ~	BFFとNext.js&Nuxt.jsの型定義~

実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~

  • 作者:吉井 健文
  • 発売日: 2019/06/26
  • メディア: 単行本(ソフトカバー)

そろそろ Next.js 手を出していく…