テック技報最新号へ →
コード

React Compiler 導入時は react-hook-form / TanStack 系のコンポーネントに 'use no memo'

React Compiler は「props や state の参照が同じなら結果も同じ」と見なして自動メモ化する。参照を保ったまま中身を変えるライブラリ(react-hook-form、TanStack Query の useInfiniteQuery、TanStack Table)を使うコンポーネントでは更新が画面に反映されなくなるので、そのファイルだけ除外する。

@kakehashi_dev · 投稿 2026-03-24
#react#frontend#react-compiler#react-hook-form#tanstack#performance

導入手順(Vite + React 19)

sh
pnpm add -D babel-plugin-react-compiler
pnpm dlx react-compiler-healthcheck --src 'src/**/*.{ts,tsx}'  # 事前に互換性を確認
ts
// vite.config.ts — 環境変数ですぐ切り戻せるようにしておく
const enableReactCompiler = process.env.REACT_COMPILER !== 'false'
export default defineConfig({
  plugins: [
    react({
      babel: enableReactCompiler ? { plugins: ['babel-plugin-react-compiler'] } : undefined,
    }),
  ],
})

React DevTools のコンポーネントツリーに「Memo」バッジが付けば効いている。

起きた症状

healthcheck は全コンポーネント成功だったのに、結合テストで次が見つかった:

  • react-hook-form: バリデーションエラーが出ない、入力が画面に反映されない、formState.isDirty が変わらない(formState は Proxy で追跡しており、form オブジェクトの参照は変わらないため)。
  • useInfiniteQuery: 「もっと読む」で data.pages が増えても再描画されないことがある。
  • useReactTable: ソートやページ送りが反映されない。

対処

tsx
'use no memo'   // ファイル先頭。このファイルのコンポーネントを自動メモ化の対象外にする
import { useForm } from 'react-hook-form'

export const MyForm = () => {
  const form = useForm()
  return <form>{/* ... */}</form>
}

付ける対象の目安:

  • useForm(やそれを包むカスタムフック)を呼ぶ、または form を props で受け取るコンポーネント
  • フォームを作って子に渡す親コンポーネント
  • useInfiniteQuery のデータに依存するコンポーネント
  • useReactTable を使うコンポーネント
    カスタムフック(useZodForm.ts など)自体には不要。使う側のコンポーネントに付ける。

効果の例(元記事の計測)

一覧画面の再レンダリング時間が 47.6ms → 3.58ms(約93%減)、バンドルは gzip で +約12KB(+3.7%)。

注意

  • この種の不具合はビルド時に分からず実行時にしか出ない。導入前に操作を伴う結合テストを用意しておく。
  • ライブラリ側の対応が進めば不要になる可能性がある。バージョンを上げたら外して試す。

出典

X の元の投稿を開く