useImperativeHandleを使ってイベントを持たせてみよう

Imperativeの意味は命令という意味です。
なので操作を命令するという意味合いになりますね。
ただし、こちらのコードは公式でも「参照を使用した命令型コードは避けるべきです」と書いているように、極力使用を控え、小技として覚えておくと良いでしょう。

useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:

引用元

引数はこんな感じ

useImperativeHandle(ref, createHandle, [deps])
  • 対象の要素のref
  • イベント
  • 依存するもの(再評価の対象)

実際に書いてみる

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

export interface InputHandles {
  focus(): void;
}

export interface InputProps extends React.HTMLAttributes<HTMLInputElement> {

}

const Input: React.RefForwardingComponent<InputHandles, InputProps> = (
  props,
  ref
) => {
  const inputRef = useRef<HTMLInputElement | null>(null);

  useImperativeHandle(ref, () => ({
    focus: () => {
      if (inputRef.current) {
        inputRef.current.focus();
      }
    },
  }));

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

export default forwardRef(Input);

呼び出して実行する

import React, { useEffect, useRef } from 'react';
import Input , { InputHandles } from './Input';

const Autofocus = (): JSX.Element => {
  const inputRef = useRef<InputHandles>(null);

  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  });

  return <Input ref={inputRef} />
}

const App = () => {
  return (
    <div className="App">
      <Autofocus />
    </div>
  );
}

export default App;

ページが読み込まれたタイミングで自動でフォーカスされるようになりました。このように要素に対してイベントを持たすことが可能です。

以下のようにonClickでフォーカスイベントを実行させることも可能です。

import React, { useRef } from 'react';
import Input , { InputHandles } from './Input';

const App = (): JSX.Element => {

  const inputRef = useRef<InputHandles>(null);
  const onClick = (): void => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div className="App">
      <button onClick={() => onClick()}>Focus</button>
      <Input ref={inputRef} />
    </div>
  );
}

export default App;

ではrefが複数存在するケースはどのようにして対処するのか?
こちらのissueが参考になりそう。

関連記事