Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ReactのFocus制御で学んだ useLayoutEffectとCallback ref

ReactのFocus制御で学んだ useLayoutEffectとCallback ref

2026/10/2(金)のReact Tokyo LTで発表したスライドです。

Avatar for Sawa Yoshioka

Sawa Yoshioka

October 03, 2026

Other Decks in Technology

Transcript

  1. whoami 吉岡 爽(Sawa Yoshioka) X: @sawa__yoshioka 愛媛県出身 2026/04 株式会社サイバーエージェントに入社 AI事業本部

    株式会社AI Shift配属 #webフロント #陶芸 #26歳 #瓶ビール #オレンジワイン 初登壇です!🙌
  2. やりたいこと:追加行への focus 制御 1 2 「割当を追加」を押す 3 その行の権限欄にフォー 未選択の空行が1行増える ��

    �� カスが移り、すぐ選べる 「DOM が更新された直後に フォーカスしたい…… • 以前は、使っていない組み合わせが自動で埋まり、意図し ない権限のまま保存できてしまっていた → 空行を追加して選んでもらう形に変更 • focus() は、スクロール領域の外にある要素も画面内まで 自動でスクロールしてくれる そうだ、useLayoutEffect だ!」
  3. 最初の実装:useLayoutEffectでfocusする 「割当を追加」で未選択の空行が増える → その行のロール欄にすぐフォーカスを移したい const listRef = useRef<HTMLDivElement>(null); const shouldFocusAddedRow

    = useRef(false); const appendAssignment = () => { shouldFocusAddedRow.current = true; addAssignment(); }; useLayoutEffect(() => { if (!shouldFocusAddedRow.current) return; 動いた!めでたしめでたし……? shouldFocusAddedRow.current = false; listRef.current?.lastElementChild?.querySelector("button")?.focus(); }, [fields.length]);
  4. レビュアーからの愛の鞭 レビューコメント [IMO] useLayoutEffect はかなり重たいので可能な限り使用を避けたい気持ち です 🙋 この用途なら effect を使わずに

    追加された最後の行だけ callback ref で mount 時に focus する 形でも実現できそうです。 愛のムチあざっす!!!
  5. 深掘り:なぜEffectはいらなかったのか ⚡ 同期的に実行 Render (何を描くか計算) DOM 更新 ref の付与 /

    useLayoutEffect Paint useEffect (画面に描画) 📌 同じタイミング・Paint の前 callback ref 版 💡 useLayoutEffect 版 要素の探し方 lastElementChild → querySelector で探す 対象の要素が自分自身を受け取る マークアップへの依存 「最後の子の最初の button」構造に暗黙依存 依存しない 仕組み 依存配列 [fields.length] で同期 Effect 不要 🔑 タイミングは同じ。違いは「Effect で DOM を探しに行く必要があるか」 出典:react.dev「You Might Not Need an Effect」/ https://ja.react.dev/learn/manipulating-the-dom-with-refs ・
  6. 修正:callback ref + フラグで追加した行にfocus {fields. map((field, index) => { const

    isLastRow = index === fields.length - 1; return ( <SelectMenu /* ❶ refに関数を渡す */ ref={(node) => { if ( node != null && isLastRow && shouldFocusAddedRow.current ) { /* ❷ インライン関数の制御 */ shouldFocusAddedRow.current = false; node.focus(); } }} // ... 眩しい.......!!!!!!! /> ); })} 1 ref に関数を渡す DOM に付いた瞬間に直接 node を受け取れる 2 インライン関数の制御 再レンダリング毎の実行を防ぐため、フラグを下ろし て「1回だけ」に制限 3 ref.current の読み出し規則 レンダー中ではなく、callback の中で安全に読む (Reactのルール) [GIF: 追加 → 新しい行にフォーカス] ✨ 挙動は変わらず、Effect と DOM 探索が消えた
  7. 学んだこと・まとめ ✅ マウント時に DOM を触るだけなら、 Effect ではなく callback ref ✅

    インラインの callback ref は毎回呼ばれる → 「1回だけ」はフラグで守る ✅ useLayoutEffect は「描画前に測らないとちらつく」ときの道具 (同じ PR でも、ポップオーバーの幅を測る処理では useLayoutEffect を残した) 💡 react-hook-form の shouldFocus を使えば もっと簡単だったかも? ありがとうございました!