useImperativeHandle と forwardRef を使うととても便利

hooks 出てきた頃に「使うのはコーナーケースなので〜」というようなことが書いてあったのでスルーしていたのだけど、ついに使うタイミングがきた

素直に React のアプリ書いてるとユースケースは確かになさそうなのだけど

モーダルで使う機能を hook に切り出している

モーダルの開け閉めをする関数をモーダルのコンポーネントが持っている

親ページでモーダルの開け閉めをするために子の開け閉め関数を呼びたい

というシーンで使えることが分かった

子コンポーネントでは forwardRef で ref を受け取って useImperativeHandle を使って、モーダル開け閉め用の関数を指定する

親コンポーネント側で useRef を使って ref を作って、子に ref={ref} で渡す

親コンポーネントで ref.current から参照する

少しハマったのは、onClick={ref.current ? ref.current.foo : undefined} みたいな感じに書くと ref の更新で render が走らないっぽいので foo を参照できない

onClick={() => ref.current ? ref.current.foo() : undefined} にするとクリックしたタイミングで評価されるので参照できる

具体的なコードは

interface Handler {
  foo(): void
}

const Child = forwardRef<Handler, Props>(props, ref) => {
  const { foo } = useSome()
  useImperativeHandler(ref, () => {
    foo: () => foo()
  }))
  return <div>...</div>
}

const App: React.FC = () => {
  const childRef = useRef({} as Handler)
  return (
  	<div>
  	  <button onClick={() => childRef.current ? childRef.current.foo() : null}>click me</button>
  	  <Child ref={childRef} />
  	</div>
  )
}

こんな感じ #20190821

参考:

https://ja.reactjs.org/docs/hooks-reference.html#useimperativehandle

https://ja.reactjs.org/docs/forwarding-refs.html