useImperativeHandle は、親が ref を通じて呼べる値を子コンポーネントが決める Hook です。DOM ノード全体ではなく、focus のような必要最小限の操作だけを公開したいときに使います。
公開する操作を絞る
React 19 では ref を通常の props として受け取れます。内部の input を直接渡さず、handle を返します。
import { useImperativeHandle, useRef, type Ref } from 'react';
type InputHandle = { focus(): void; clear(): void };
function SearchInput({ ref }: { ref: Ref<InputHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => { if (inputRef.current) inputRef.current.value = ''; },
}), []);
return <input ref={inputRef} type="search" />;
}
親は次のように handle の型だけに依存します。
function SearchPage() {
const searchRef = useRef<InputHandle>(null);
return <><SearchInput ref={searchRef} /><button onClick={() => searchRef.current?.focus()}>検索する</button></>;
}
親が DOM の value や classList を触れないため、子の内部構造を変えても影響範囲を小さくできます。
React 18 以前も対象にするライブラリでは、forwardRef で ref を受け取る書き方を使います。
この例で親が知っているのは focus と clear だけです。input が textarea や独自の入力部品へ変わっても、同じ操作を提供し続ければ親を直す必要はありません。これは内部実装を隠すための小さな public API です。
使う判断
通常は props で状態とイベントを渡す方が、データフローを追いやすくなります。フォーカス、スクロール、メディア再生のような命令的なブラウザ操作に限定して使います。親が子の内部 state を広く操作する API は、コンポーネントの境界を壊す兆候です。
落とし穴
handle を生成する関数で参照する reactive な値は依存配列へ含めます。省略すると毎レンダーで handle が作り直されます。公開するメソッドは少なく保ち、DOM 実装を将来変更しても親が壊れない境界にします。
特に clear() が入力値を直接書き換えるなら、入力が controlled か uncontrolled かを確認します。value を state から渡す controlled input では、DOM だけを空にしても次のレンダーで元に戻ります。その場合は親が state を更新する props API の方が正しい設計です。
ref を使わない設計を先に考える
「親のボタンで子を開く」「子の値を親がリセットする」といった要求は、open や onChange を props で渡す controlled component にできることが多いです。命令を命令のまま公開するのではなく、親が持つべき state とイベントへ翻訳できないかを検討します。ref はフォーカスやスクロールのように、宣言的な props へ自然に変換できない操作の最後の選択肢です。
まとめ
useImperativeHandle は ref の公開面を制限する Hook です。props で表現できない操作だけを、小さなメソッドとして公開しましょう。