forwardRef は、親コンポーネントに対して DOM ノードを ref として公開できるようにします。

const SomeComponent = forwardRef(render)

リファレンス

forwardRef(render)

forwardRef() を呼び出すことで、コンポーネントが ref を受け取ってそれを子コンポーネントに転送 (forward) できるようになります。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});

さらに例を見る

引数

  • render: コンポーネントのレンダー関数です。React はこの関数を親から受け取った props および ref とともに呼び出します。返す JSX がコンポーネントの出力となります。

返り値

forwardRef は JSX でレンダーできる React コンポーネントを返します。プレーンな関数として定義された React コンポーネントとは異なり、forwardRef によって返されるコンポーネントは ref 属性を受け取ることもできます。

注意点


render 関数

forwardRef は引数としてレンダー関数を受け取ります。React はこの関数を props および ref とともに呼び出します。

const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});

引数

  • props: 親コンポーネントから渡された props です。

  • ref: 親コンポーネントから渡された ref 属性です。ref はオブジェクトの場合と関数の場合があります。親コンポーネントが ref を渡していない場合は null になります。受け取った ref は、別のコンポーネントに渡すか、useImperativeHandle に渡します。

返り値

forwardRef は JSX でレンダーできる React コンポーネントを返します。プレーンな関数として定義された React コンポーネントとは異なり、forwardRef によって返されるコンポーネントは ref 属性を受け取ることができます。


使用法

親コンポーネントに DOM ノードを公開する

デフォルトでは、各コンポーネント内の DOM ノードはプライベートです。しかし、時には親に DOM ノードを公開することが有用な場合があります。例えば、ノードにフォーカスを当てることを許可したい場合です。これを明示的に許可するために、コンポーネント定義を forwardRef() でラップします。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});

props の後の第 2 引数として ref が渡されます。公開したい DOM ノードにそれを渡してください。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});

これで、親の Form コンポーネントが、MyInput によって公開された <input> DOM ノードにアクセスできるようになります。

function Form() {
const ref = useRef(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

この Form コンポーネントは MyInput に ref を渡しています。MyInput コンポーネントはその ref をブラウザの <input> タグに転送しています。その結果、Form コンポーネントはこの <input> DOM ノードにアクセスし、focus() を呼び出すことができるようになります。

コンポーネント内の DOM ノードへの ref を公開することで、後でコンポーネントの内部を変更するのが難しくなることに注意してください。通常は、ボタンやテキスト入力フィールドなどの再利用可能な低レベルコンポーネントからは DOM ノードの公開を行いますが、アバターやコメントのようなアプリケーションレベルのコンポーネントでは行いません。

ref の転送の例

例 1/2:
テキスト入力フィールドにフォーカス

ボタンをクリックすると、入力フィールドにフォーカスが当てられます。Form コンポーネントは ref を定義し、それを MyInput コンポーネントに渡します。MyInput コンポーネントはその ref をブラウザの <input> に転送します。これにより、Form コンポーネントは <input> にフォーカスを当てられるようになります。

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


複数コンポーネントを経由した ref の転送

ref を DOM ノードに転送する代わりに、独自コンポーネントである MyInput に転送することもできます。

const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});

さらにその MyInput コンポーネントが自身の <input> に ref を転送すれば、FormField への ref はその <input> への参照を受け取ることになります。

function Form() {
const ref = useRef(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

Form コンポーネントは ref を定義し、それを FormField に渡しています。FormField コンポーネントはその ref を MyInput に転送し、MyInput はそれをブラウザの <input> DOM ノードに転送しています。これで Form が DOM ノードにアクセスできるようになります。

import { useRef } from 'react';
import FormField from './FormField.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <FormField label="Enter your name:" ref={ref} isRequired={true} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


DOM ノードの代わりに命令型ハンドルを公開する

DOM ノードをまるごと公開する代わりに、使用できるメソッドを制限したカスタムオブジェクトである、命令型ハンドル (imperative handle) を公開することができます。これを行うには、DOM ノードを保持するための別の ref を定義します。

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

// ...

return <input {...props} ref={inputRef} />;
});

そして受け取った ref を useImperativeHandle に渡し、ref で公開したい値を指定します。

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);

return <input {...props} ref={inputRef} />;
});

何らかのコンポーネントが MyInput への ref を取得すると、DOM ノードの代わりにあなたが書いた { focus, scrollIntoView } というオブジェクトを受け取ります。これにより、DOM ノードについて公開する情報を最小限に制限することができます。

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

命令型ハンドルの使用について詳しく読む

落とし穴

ref の過度な使用に注意してください。ref は、props として表現できない、命令型の動作にのみ使用するべきです。例えば、ノードへのスクロール、ノードへのフォーカス、アニメーションのトリガ、テキストの選択などです。

何かを props として表現できる場合は、ref を使用すべきではありません。例えば、Modal コンポーネントから { open, close } のような命令型のハンドルを公開するのではなく、<Modal isOpen={isOpen} /> のように、isOpen を props として受け取る方が良いでしょう。命令型の動作を props として公開する際にはエフェクトが役立ちます。


トラブルシューティング

コンポーネントを forwardRef でラップしているのに、ref が常に null になる

これは通常、受け取った ref を実際に使用するのを忘れていることを意味します。

例えば、このコンポーネントは ref を全く使用していません:

const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});

修正するにはこの ref を、DOM ノードか、ref を受け入れることができる別のコンポーネントに渡します。

const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});

一部のロジックが条件付きである場合にも、MyInput への ref が null になることがあります。

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});

showInput が false の場合、ref はどのノードにも転送されないため、MyInput への ref は空のままになります。特に、以下の例のように条件が別のコンポーネント、例えば Panel の中に隠されている場合、これを見落としがちです。

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});