forwardRef を使うと、親コンポーネントは自分のコンポーネントを通して、その中のDOM要素に ref を渡せます。そうすれば親はその要素で focus() や scrollIntoView() などのメソッドを呼べます。React 19ではもう必要ありません。関数コンポーネントが ref を普通のpropとして受け取るからです。それでも forwardRef は動き、既存のコードの多くで目にするので、このページでは両方を紹介します。
forwardRef は、props と親が渡した ref という2つの引数を持つ描画関数を受け取ります。ref は props の中にはなく、別に届きます。Focusをクリックすると、inputRef.current が本物の <input> ノードであることがコンソールで確かめられます。
React 19の書き方:refはprop
React 19以降、関数コンポーネントはほかのpropと同じように ref をpropsで受け取ります。ラッパーも2つ目の引数もいりません。
何か入力してLog valueをクリックしてください。親が入力欄の現在のテキストをDOMノードから直接読みます。<input> から ref={ref} を削除してFocusをクリックすると、コンソールにエラーが表示されます。inputRef.current が null になったからです。
forwardRef(全バージョン) | ref prop(React 19以降) | |
|---|---|---|
| コンポーネントがrefを受け取る方法 | 2つ目の引数、(props, ref) | props.ref |
| ラッパーが必要か | はい | いいえ |
useImperativeHandle と使えるか | はい | はい |
| 状態 | 動くが、非推奨になる予定 | 新しいコードの書き方 |
子が無視したrefはnullのまま
コンポーネントに ref を渡すだけでは何も起きません。コンポーネントがそれを要素に付ける必要があります。付けなければ、ref.current は null のままです。
コンソールには Forgetful ref: null と Careful ref: INPUT が表示されます。自作のコンポーネントで「ref.currentがnull」になる一番よくある原因がこれです。React 19より前は、refが関数に渡されることがなかったので、forwardRef で包まない限り Careful も null になっていました。
useImperativeHandle:小さなAPIを公開する
親にDOMノード全体を渡すと、親はそれに何でもできてしまいます。スタイルを変える、子を取り除く、非公開にしておきたかった値を読む、などです。useImperativeHandle を使えば、代わりに ref.current に何を入れるかを自分で決められます。
最後のボタンは ["focus", "clear"] を記録します。親が手にするのはその2つのメソッドだけで、入力欄のノードではありません。オブジェクトに select() メソッドを追加し、それを呼ぶボタンを足すと、親ができることがちょうど1つ増えます。
子は本物の入力欄のために自分のref inputRef を持ち、ハンドルがそれを包みます。第3引数は useEffect と同じような依存配列で、その値が変わるとハンドルが作り直されます。
これは控えめに使ってください。親がしたいこと(開く、閉じる、エラーを表示する)のほとんどは、isOpen や error のようなpropsのほうが適しています。命令的なメソッドは、フォーカス、スクロール、動画の再生、アニメーションの開始など、自然なpropがない操作のためのものです。
子の中でもrefを使う
子が自分の仕事のために同じDOMノードを必要とすることがあります。たとえばサイズを測ったり、エラーの後にフォーカスしたりしつつ、親もそのノードへのrefを持っている場合です。1つの ref 属性が受け取れる値は1つだけなので、refコールバックで2つをまとめます。
function AutoGrowTextarea({ ref, ...props }) {
const localRef = useRef(null);
function setRefs(node) {
localRef.current = node;
if (typeof ref === 'function') ref(node);
else if (ref) ref.current = node;
}
return <textarea ref={setRefs} {...props} />;
}
親の ref は useRef のオブジェクトの場合も関数の場合もあるので、両方に対応します。親がいくつかの操作だけを必要とするなら、子がノードを自分だけで持てる上の useImperativeHandle のほうがすっきりします。
複数の層を通してrefを渡す
refは1つのコンポーネントずつ伝わります。Form が Field を描画し、それが TextInput を描画し、それが <input> を描画するなら、それぞれのコンポーネントが次へ ref を渡す必要があります。React 19では転送するpropが1つ増えるだけです(<TextInput ref={ref} />)。forwardRef では、すべての層を包む必要がありました。古いバージョンでは、propsのスプレッド({...props})ではrefは運ばれません。React 19より前は、ref が props に含まれることはなかったからです。
これが問題になるのは主にデザインシステムのコンポーネントです。ネイティブ要素を包む Button、Input、Select はrefを転送すべきです。そうすれば、それを使うアプリがフォーカスしたり、サイズを測ったり、ポップオーバーを配置するライブラリに渡したりできます。
forwardRefから移行する
変更は機械的です。ラッパーを取り除き、propsから ref を読みます。
// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
return <button ref={ref} className={variant} {...props} />;
});
// After (React 19)
function Button({ variant, ref, ...props }) {
return <button ref={ref} className={variant} {...props} />;
}
急ぐ必要はありません。forwardRef はReact 19でも動き続けますし、React 18をサポートしなければならないライブラリは使い続ける必要があります。React 18は ref をpropとして渡さないからです。クラスコンポーネントには影響しません。クラスコンポーネントに付けたrefは、今もコンポーネントのインスタンスを指します。
TypeScript
forwardRef では、型引数はrefの型、propsの順で指定します。
import { forwardRef } from 'react';
type FancyInputProps = { label: string };
const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
function FancyInput({ label }, ref) {
return <input ref={ref} aria-label={label} />;
}
);
React 19では、ref にほかのpropと同じように型を付けます。ComponentProps<'input'> にはすでに含まれています。
import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';
function FancyInput(props: ComponentProps<'input'>) {
return <input {...props} />;
}
type SearchHandle = { focus: () => void; clear: () => void };
function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
}
// In the parent
const searchRef = useRef<SearchHandle>(null);
ref自体(DOMへのアクセス、レンダリングなしで保たれる値、refコールバック)についてはuseRefのページで扱っています。コンポーネントへの型付け全般は、ReactとTypeScriptを参照してください。
よくある質問
ReactのforwardRefは何をするものですか?
関数コンポーネントを包み、親が渡した ref が2つ目の引数 (props, ref) としてコンポーネントに届くようにします。コンポーネントはそのrefをDOMノードに付けるので、親はそこで focus() のようなメソッドを呼べます。
React 19でforwardRefは非推奨ですか?
まだ非推奨ではなく、今も動きます。React 19は関数コンポーネントに ref を普通のpropとして渡すので、新しいコードでは不要です。Reactチームは、将来のバージョンでforwardRefを非推奨にする予定だと述べています。
自作のコンポーネントでrefがnullになるのはなぜですか?
コンポーネントはrefを受け取ったのに、どの要素にも付けていないからです。中のDOMノードに付けてください:<input ref={ref} />。React 19より前は、コンポーネントを forwardRef で包まない限り、refはそもそも渡されませんでした。
useImperativeHandleは何に使いますか?
親が ref.current で見るものを置き換えます。DOMノード全体の代わりに、focus や clear のように自分で選んだメソッドだけを持つオブジェクトを返します。
React 19ではクラスコンポーネントもrefをpropとして受け取りますか?
いいえ。クラスコンポーネントに付けたrefは、今もコンポーネントのインスタンスを指します。この変更は関数コンポーネントだけに適用されます。