React・仕事で使うために

残りのフックと、いつ使うか

ここまでで出てこなかったフックをまとめて片づけます。使う場面がはっきりしているuseId、めったに使わないが必要なときは必須のuseLayoutEffect、そしてライブラリ側のuseImperativeHandleとuseSyncExternalStore。

ここまでで、実務で使うフックはほぼ出そろいました。この回は残りを片づけます

すべてを覚える必要はありません。「名前と、どういうときに出てくるか」が分かっていれば、必要になったときに調べられます。

useId——重複しないidを作る

フォームを書くと必ず必要になります。

const id = useId()

<label htmlFor={id}>名前</label>
<input id={id} />

<label><input>htmlForidでつなぎます。ところがその部品を同じ画面に2つ置いた瞬間、idが重複します。重複すると、ラベルを押しても後ろのほうにフォーカスが行きません。

実際にuseId()が返した値を見ると、_r_0__r_1_のように呼ばれるたびに違う値になっていました。同じ部品を2つ置いても衝突しません。

複数の欄があるときは、接尾辞を足して1つのuseIdを使い回すのが定番です。

const id = useId()

<input id={`${id}-name`} />
<input id={`${id}-area`} />

useLayoutEffect——描かれる前に測る

useEffect画面に描かれたあとに走ります。ふだんはそれで困りません。

困るのは測ってから位置を直すときです。useEffectだと「一瞬ずれた状態が見えてから、直る」——つまりちらつきます

const boxRef = useRef(null)
const [width, setWidth] = useState(0)

useLayoutEffect(() => {
  setWidth(boxRef.current?.getBoundingClientRect().width ?? 0)
}, [])

useLayoutEffect描かれる直前に同期的に走るので、ずれた状態が表示されません。実際に測ると、最初の表示の時点でwidth: 1216が入っていました。

走るタイミング使うとき
useEffect画面に描かれたあとふつうはこちら。通信・購読・タイマー
useLayoutEffect描かれる直前(同期)測って、その場で位置を直す(ツールチップ・吹き出し)

useImperativeHandle——部品の外に操作を公開する

refで渡ってくるのは、ふつうDOMそのものです。自分で作った操作を渡したいときに使います。

function Modal({ ref }) {
  const dialogRef = useRef(null)

  useImperativeHandle(ref, () => ({
    open: () => dialogRef.current?.showModal(),
    close: () => dialogRef.current?.close(),
  }))

  return <dialog ref={dialogRef}>…</dialog>
}

親からはmodalRef.current?.open()と書けます。DOM全部ではなく、決めた操作だけを外に出すのがねらいです。

useSyncExternalStore——Reactの外の値を購読する

Reactの外にある値をコンポーネントで使うためのフックです。

function useIsOnline() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback)
      window.addEventListener('offline', callback)
      return () => {
        window.removeEventListener('online', callback)
        window.removeEventListener('offline', callback)
      }
    },
    () => navigator.onLine,      // いまの値
    () => true,                  // サーバー側での値
  )
}

引数は購読する関数・値を読む関数・サーバー側で読む関数の3つです。

自分で書く機会はそう多くありませんが、知っておく価値はあります状態管理ライブラリの回で入れたZustandは、内部でこれを使ってストアとReactをつないでいます。「Providerが無いのに再描画される」仕組みの正体がこれです。

全体の早見表

ここまでで出てきたフックをまとめます。

フック何のため出た回
useState画面に出す値を持つstate
useEffect外の世界と同期するeffect
useContext深いところへ値を配るcontext
useRefDOMの操作・覚えておくだけの値refs-and-portals
useMemo / useCallback計算と関数を覚えておくrendering
useReducer複雑なstateの変え方をまとめるuse-reducer
useId重複しないidを作るこの回
useLayoutEffect描かれる前に測って直すこの回
useImperativeHandle部品の操作を外に公開するこの回
useSyncExternalStoreReactの外の値を購読するこの回
useTransition / useDeferredValue優先度を下げるreact19 / large-lists
useActionState / useOptimisticフォームの送信と先出し表示react19
usePromiseやcontextを読むreact19

毎日使うのは上の4つだけです。残りは「そういうものがある」と知っていれば十分で、それがこの回の目的です。

Reactカスタムフックにまとめるカスタムフックの回。ここに出てきたフックも、まとめて自分のフックに包めます

公式ドキュメントも見てみる

ja.react.dev外部サイト組み込みの React フック – ReactReact公式のフック一覧。この回で扱ったものも含めて、すべてのフックがここから引けます

このレッスンのまとめ

  1. useIdは重複しないidを作る。<label htmlFor><input id>をつなぐのに必須
  2. Math.random()で代用しない(描き直しで変わる・サーバー側とずれる
  3. useLayoutEffectは描かれる直前に同期で走る。測って直すときだけ
  4. 既定はuseEffectuseLayoutEffect表示を遅らせる
  5. useImperativeHandleは部品の操作を外に公開する。まずpropsで足りないか考える
  6. useSyncExternalStoreはReactの外の値を購読する。Zustandの中身がこれ
  7. 毎日使うのはuseStateuseEffectuseContextuseRef。残りは名前を知っていれば十分

次は、エラーで画面全体が消えないようにする回です。

やってみよう:ラベルとidをuseIdでつなぐ

フォームのidを、手書きからuseIdに置きかえます。

  1. AddBirdForm<label><input>を、htmlForidでつなぐ
  2. idはuseId()+接尾辞(${id}-name${id}-area)で作る
  3. 同じフォームを一時的に2つ並べて、上のラベルを押したら上の欄にフォーカスが行くことを確認する
  4. 試しにidを"name"のような固定値にして、3番が壊れることを確認する
const id = useId()

<label htmlFor={`${id}-name`}>名前</label>
<input id={`${id}-name`} value={name} onChange={handleNameChange} />

4番で「ラベルを押すと下のフォームの欄にフォーカスが行く」のが見えるはずです。部品を2つ置いた瞬間に壊れる——これがuseIdの存在理由です。確認できたら2つ目のフォームは外します。

よくある質問

フックは全部覚える必要がありますか?
毎日使うのはuseState・useEffect・useContext・useRefくらいです。残りは名前と用途を知っていて、必要になったときに調べられれば十分です。ただしuseIdだけは、フォームを作るなら日常的に使います。
idにMath.randomを使ってはいけませんか?
いけません。描き直しのたびに変わりますし、サーバー側とブラウザ側で違う値になって表示が食い違います。useIdはその両方を解決します。
useEffectとuseLayoutEffectはどちらを使いますか?
既定はuseEffectです。useLayoutEffectは画面に描かれる前に同期的に走るので、位置や大きさを測ってその場で直す場合だけ使います。乱用すると描画が遅くなります。
useSyncExternalStoreは自分で書きますか?
ふつうは書きません。ZustandやRedux、react-routerの内部で使われているフックです。ブラウザの状態を購読したいときに直接使うことはあります。