Reactで関数コンポーネントを作成する際、同じDOMノードに対して
- forwardRefでDOMノードをフォワーディングする
- useRefを使ってDOMノードを操作する
を両立させたい場合があります。
その良い方法が見つかったので、ここに残しておきます。
useImperativeHandleを使います
気付いてみれば当たり前のことですが、useImperativeHandleを使えばうまく実装することができます。
useRefで保持しているDOMノードを、useImperativeHandleを使ってフォワーディングします。
const RefSample = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
// DOMノードを保持するref
const inputRef = useRef<HTMLInputElement>(null);
// useImperativeHandleでフォワーディング
useImperativeHandle(ref, () => inputRef.current!);
const focusHandler = useCallback(() => {
inputRef.current?.focus();
}, []);
return (
<div>
<button onClick={focusHandler}>focus</button>
<input ref={inputRef} type={"text"} />
</div>
);
});
これで、このコンポーネントを使う際にrefを渡すことができるようになります。
const App = () => {
const ref = useRef<HTMLInputElement>(null);
const blurHandler = useCallback(() => {
ref.current?.blur();
}, []);
return (
<div>
<RefSample ref={ref} />
<button onClick={blurHandler}>blur</button>
</div>
);
};
補足: useImperativeHandleについて
useImperativeHandleの第2引数には関数を渡しますが、この関数がいつ実行されるのか公式ページには載っていないようなので、軽く調査しました。
どうやら、HTMLのレンダリングが終了してから、useLayoutEffectに渡した関数が呼び出されるまでの間のタイミングで、かつReactのUIツリーの末端にあるエレメントから実行されていくようです。
関連
これに関連して、以下の記事も作成しました。