Home About Contact
React , TypeScript

React useRef を使って親コンポーネントから子コンポーネントの状態を取得する

子コンポーネントは状態を管理させないで、 親コンポーネントですべて状態を管理する方法が基本だとは思う。

しかし ここでは、子コンポーネントで状態を管理して、 親コンポーネントからそれを任意のタイミングで把握する方法を調べます。

使用した環境:

$ node --version
v22.17.0
$ npm -version
10.9.2

プロジェクトを作成

hello-use-ref プロジェクトを作成:

npm create vite hello-use-ref --template react-ts

質問されるので、React + TypeScript を選択した。

プロジェクトルートへ移動:

cd hello-use-ref

依存するモジュールを入れる:

npm install

ポートを 8000 で起動するように修正:

package.json:

"scripts": {
  "dev": "vite --port 8000",      

--port 8000 を追加する。

起動して確かめる:

npm run dev

ブラウザで http://localhost:8000/ にアクセスします。

とりあえず Save ボタンだけのアプリを作成

src/App.tsx を次のように書きかえる:

import React from 'react'

const MyButton = (props: {
    label: string
    onClick: ()=>void
}): React.ReactElement => {
    return (
        <button
            type="button"
            onClick={()=>props.onClick()}
        >{props.label}</button>
    )
}

const App = (): React.ReactElement => {
    const onSaveButtonClick = ():void =>{
        console.log("Save Button is Clicked")
    }

    return (
        <>
            <MyButton
                label="Save"
                onClick={onSaveButtonClick}
            />
        </>
    )
}

export default App

起動して作動を確かめる:

$ npm run dev

子コンポーネント作成

Yes / No の2つの状態を持つ子コンポーネントを作成します。

事前準備として MyRadioButton を用意:

const MyRadioButton = (props: {
    id: string
    name: string
    label: string
    checked: boolean
    onChange: ()=>void
}): React.ReactElement => {
    return (
        <>
            <input
                type="radio"
                name={props.name}
                id={props.id}
                checked={props.checked}
                onChange={()=>props.onChange()}
            />
    
            <label
                htmlFor={props.id}
            >{props.label}</label>
        </>
    )
}

本体の子コンポーネントとなる YesNoPanel を用意:

type YesNo = 'Yes' | 'No'

const YesNoPanel = (): React.ReactElement => {
    const [yesNoValue, setYesNoValue] = useState<YesNo>('Yes')

    const onYesRadioButtonChange = ():void => { setYesNoValue('Yes') }
    const onNoRadioButtonChange = ():void => { setYesNoValue('No') }

    return (
        <div>
            <MyRadioButton
                id="YesOption"
                name="YesNoRadioButtons"
                label={"Yes"}
                checked={(yesNoValue==='Yes')}
                onChange={onYesRadioButtonChange}
            />
            <MyRadioButton
                id="NoOption"
                name="YesNoRadioButtons"
                label={"No"}
                checked={(yesNoValue==='No')}
                onChange={onNoRadioButtonChange}
            />
        </div>
    )
}

yes-no

useRef を使って Save ボタンをクリックしたときに YesNoPanel が Yes か No どちらの状態にあるか調べる

子コンポーネントから値を受け取るための type YesNoCallbackFn を定義:

type YesNoCallbackFn = () => YesNo;

App を修正:

const App = (): React.ReactElement => {
    const yesNoCallbackFnRef = useRef<YesNoCallbackFn | undefined>(undefined);

    const onSaveButtonClick = ():void =>{
        const fn: YesNoCallbackFn | undefined = yesNoCallbackFnRef.current
        if (fn!==undefined) {
            const yesOrNo: YesNo = fn()
            console.log(`@ ${yesOrNo}`)
        }
    }

    ...

    return (
        <>
            <YesNoPanel
                yesNoCallbackFnRef={yesNoCallbackFnRef}
            />

        ...

useRef を使って yesNoCallbackFnRef を用意。 これを YesNoPanel に渡すのと同時に onSaveButtonClick 時には yesNoCallbackFnRef.current を使って YesNoPanel の現在の状態(Yes か No か)を取得しています。 結果を確認するために console.log しています。

YesNoPanel を修正:

const YesNoPanel = (props: {
    yesNoCallbackFnRef: ReturnType<typeof useRef<YesNoCallbackFn | undefined>>
}): React.ReactElement => {

    const [yesNoValue, setYesNoValue] = useState<YesNo>('Yes')

    useEffect(()=>{
        const fn: YesNoCallbackFn = ():YesNo =>{ return yesNoValue }
        props.yesNoCallbackFnRef.current = fn
    }, [yesNoValue])

yesNoCallbackFnRef の型は ReturnType<typeof useRef<YesNoCallbackFn | undefined>> だとのこと。(claude に聞いた。)

yesNoValue が変わるたびに useEffect でコールバックする関数を更新する。 こうすることで、親コンポーネントから useRef で定義した yesNoCallbackFnRef.current を適用すること(コールするといえばいいのか)で 子コンポーネントの状態が Yes だったか No だったかを受け取ることができる。

その部分は別に関数ではなく、単に YesNo タイプの値でよい気はする。 関数にすることでうれしいことあるのかな?(わからない) とりあえずこの例では、関数を使うメリットはないのでその点を修正したコードを最後に追記する。

まとめ

完成した App.tsx:

import React, { useState, useRef, useEffect } from 'react'

const MyButton = (props: {
    label: string
    onClick: ()=>void
}): React.ReactElement => {
    return (
        <button
            type="button"
            onClick={()=>props.onClick()}
        >{props.label}</button>
    )
}

const MyRadioButton = (props: {
    id: string
    name: string
    label: string
    checked: boolean
    onChange: ()=>void
}): React.ReactElement => {
    return (
        <>
            <input
                type="radio"
                name={props.name}
                id={props.id}
                checked={props.checked}
                onChange={()=>props.onChange()}
            />
    
            <label
                htmlFor={props.id}
            >{props.label}</label>
        </>
    )
}


type YesNo = 'Yes' | 'No'
type YesNoCallbackFn = () => YesNo;

const YesNoPanel = (props: {
    yesNoCallbackFnRef: ReturnType<typeof useRef<YesNoCallbackFn | undefined>>
}): React.ReactElement => {

    const [yesNoValue, setYesNoValue] = useState<YesNo>('Yes')

    useEffect(()=>{
        const fn: YesNoCallbackFn = ():YesNo =>{ return yesNoValue }
        props.yesNoCallbackFnRef.current = fn
    }, [yesNoValue])



    const onYesRadioButtonChange = ():void => { setYesNoValue('Yes') }
    const onNoRadioButtonChange = ():void => { setYesNoValue('No') }

    return (
        <div>
            <MyRadioButton
                id="YesOption"
                name="YesNoRadioButtons"
                label={"Yes"}
                checked={(yesNoValue==='Yes')}
                onChange={onYesRadioButtonChange}
            />
            <MyRadioButton
                id="NoOption"
                name="YesNoRadioButtons"
                label={"No"}
                checked={(yesNoValue==='No')}
                onChange={onNoRadioButtonChange}
            />
        </div>
    )
}

const App = (): React.ReactElement => {
    const yesNoCallbackFnRef = useRef<YesNoCallbackFn | undefined>(undefined);

    const onSaveButtonClick = ():void =>{
        const fn: YesNoCallbackFn | undefined = yesNoCallbackFnRef.current
        if (fn!==undefined) {
            const yesOrNo: YesNo = fn()
            console.log(`@ ${yesOrNo}`)
        }
    }

    return (
        <>
            <YesNoPanel
                yesNoCallbackFnRef={yesNoCallbackFnRef}
            />
            <MyButton
                label="Save"
                onClick={onSaveButtonClick}
            />
        </>
    )
}

export default App

追伸 関数ではなく単なる値にした

YesNoPanel の状態を知るためにはわざわざ関数を使う必要もないので、単に YesNo の値を current にセットするように変更した改善版 App.tsx:

import React, { useState, useRef, useEffect } from 'react'

const MyButton = (props: {
    label: string
    onClick: ()=>void
}): React.ReactElement => {
    return (
        <button
            type="button"
            onClick={()=>props.onClick()}
        >{props.label}</button>
    )
}

const MyRadioButton = (props: {
    id: string
    name: string
    label: string
    checked: boolean
    onChange: ()=>void
}): React.ReactElement => {
    return (
        <>
            <input
                type="radio"
                name={props.name}
                id={props.id}
                checked={props.checked}
                onChange={()=>props.onChange()}
            />
    
            <label
                htmlFor={props.id}
            >{props.label}</label>
        </>
    )
}


type YesNo = 'Yes' | 'No'

const YesNoPanel = (props: {
    yesNoRef: ReturnType<typeof useRef<YesNo | undefined>>
}): React.ReactElement => {

    const [yesNoValue, setYesNoValue] = useState<YesNo>('Yes')

    useEffect(()=>{
        props.yesNoRef.current = yesNoValue
    }, [yesNoValue])


    const onYesRadioButtonChange = ():void => { setYesNoValue('Yes') }
    const onNoRadioButtonChange = ():void => { setYesNoValue('No') }

    return (
        <div>
            <MyRadioButton
                id="YesOption"
                name="YesNoRadioButtons"
                label={"Yes"}
                checked={(yesNoValue==='Yes')}
                onChange={onYesRadioButtonChange}
            />
            <MyRadioButton
                id="NoOption"
                name="YesNoRadioButtons"
                label={"No"}
                checked={(yesNoValue==='No')}
                onChange={onNoRadioButtonChange}
            />
        </div>
    )
}

const App = (): React.ReactElement => {
    const yesNoRef = useRef<YesNo | undefined>(undefined);

    const onSaveButtonClick = ():void =>{
        const yesOrNo: YesNo | undefined = yesNoRef.current
        if (yesOrNo!==undefined) {
            console.log(`@ ${yesOrNo}`)
        }
    }

    return (
        <>
            <YesNoPanel
                yesNoRef={yesNoRef}
            />
            <MyButton
                label="Save"
                onClick={onSaveButtonClick}
            />
        </>
    )
}

export default App

以上です。