React Hooks を使ったアプリでボタンの状態やフィールドへのフォーカスなど ref
を使った操作を、そのボタンやフィールドの親コンポーネントから呼び出したい時のメモ。
日本語が不自由なのでサンプルを
例えばボタンを押した時に非同期処理をするから、処理が完了するまでボタンを disabled
にしておきたいみたいな時
単純に useRef したもの props で渡しても上手くいかない
import React, { useRef } from 'react'; import SubmitButton from './SubmitButton'; function PostList(props) { const submitBtn = useRef(null); const onSubmitHandler = async (e) => { submitBtn.current.disabled = true; try { // ...非同期処理 } catch (err) { ... } submitBtn.current.disabled = false; }; return ( <> {/* ...処理 */} <SubmitButton ref={submitBtn} onClick={onSubmitHandler} /> </> ); }
SubmitButton.js
import React from 'react'; function SubmitButton(props) { return ( <button className="p-submitBtn" ref={props.ref} onClick={props.onClick} />SUBMIT</button> ); } export default SubmitButton;
useRef
したものを props
で渡してボタンの ref
属性に付けても親コンポーネントでは null
になってしまいうまくいかない。
上記の例ではボタンを押した時に実行される onSubmitHandler
内で submitBtn.current
が null
なのでエラーになってしまい意図したとおりに動作しない。
const onSubmitHandler = async (e) => { // submitBtn.current は null submitBtn.current.disabled = true; // ... };
Forwarding Refs を使う
useImperativeHandle
customizes the instance value that is exposed to parent components when usingref
. As always, imperative code using refs should be avoided in most cases.useImperativeHandle
should be used withforwardRef
ref. Hooks API Reference – React #useimperativehandle
React Hooks では子コンポーネント内で useImperativeHandle
を使い ref.current
から呼び出せる関数を定義して、子コンポーネント自体を forwardRef()
でに渡しておくことで親コンポーネントのref.current
を通じて子コンポーネントの ref
にアクセスすることができる
import React, { useRef } from 'react'; import SubmitButton from './SubmitButton'; function PostList(props) { const submitBtn = useRef(null); const onSubmitHandler = async (e) => { // ref.current を通じて関数を呼び出すように変更 submitBtn.current.disabled(false); try { // ...非同期処理 } catch (err) { ... } submitBtn.current.disabled(false); }; return ( <> {/* ...処理 */} <SubmitButton ref={submitBtn} onClick={onSubmitHandler} /> </> ); }
SubmitButton.js
import React, { useRef, forwardRef, useImperativeHandle } from 'react'; function SubmitButton(props, ref) { const btnRef = useRef(); useImperativeHandle(ref, () => { // 親コンポーネントの ref.current から実行できる関数を定義したオブジェクトを返す return { disabled: (flg) => btnRef.current.disabled = !!flg } }); return ( <button className="p-submitBtn" ref={btnRef} onClick={props.onClick} />SUBMIT</button> ); } // コンポーネントを forwardRef でラップ SubmitButton = forwardRef(SubmitButton); export default SubmitButton;
forwardRef
を使う場合 const
で関数定義するほうが見やすいかも?
const SubmitButton = React.forwardRef((props, ref) => { // ... });
これでボタンをクリックした時に、非同期処理が完了するまでボタンを disabled
できるようになりました。
ポエム
useImperativeHandle
で返すオブジェクトに setter
として関数を定義したら親コンポーネントからプロパティみたいにアクセスできるのかな? チョット気になってきたので、時間があるときにでも試してみたい。
react-create-app
もデフォルトで Function Component になったし、GW中にだいぶ React Hooks と仲良くなれたので、まとめたものを近々書いておきたい。 (まだ触れてない Hooks もたくさんあるけど…
[参考]

大人用品鼻フック レザーバインディングおもちゃ 合金フックノーズ女性のセックスおもちゃ
- メディア: ヘルスケア&ケア用品