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 を使う
useImperativeHandlecustomizes the instance value that is exposed to parent components when usingref. As always, imperative code using refs should be avoided in most cases.useImperativeHandleshould 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 もたくさんあるけど…
[参考]

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