28
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【TypeScript】useContextとuseStateを組み合わせて、子孫コンポーネントから直接先祖コンポーネントのstateを編集する

Posted at

実務でReactをいろいろ経験させてもらってから、ようやくReact Hooksの便利さに気づき始めたラスカルです。こんにちは。

今回は、タイトルの通りではあるのですが、useContextとuseStateを組み合わせることで、子孫コンポーネントから、propsのバケツリレーをせずに先祖コンポーネントのstateを更新する実装をTypeScriptで実装したいと思います。

(強調したのは、jsでの実装はあるもののtsでの実装がすぐに見つからなかったからです😩)

##前提条件

import React, { useState } from 'react'

//親コンポーネント
const Parent: React.FC = () => {
    const [count, setCount] = useState(0)
    return (
        <>
         <Child />
        </>
    )
}
 
//子コンポーネント
const Child: React.FC = () => {
    return (
        <>
         <GrandChild />
        </>
    )
}

//孫コンポーネント
const GrandChild: React.FC = () => {
    return (
        <>
         <button></button>
         <button></button>
        </>
    )
}

親コンポーネントはuseStateを使って、状態管理をしています。
このcountの値を更新するためには、setCountを使ってあげる必要がありますね。
これを孫コンポーネントでボタンをおすイベントで、行いたいというわけです。

結論

useContextを使い、propsのバケツリレーをしなくても、孫要素から直接親要素のstateを更新できるようにします。

手法としてはuseContextで直接count, setCountを孫コンポーネントに送ってしまいます。

import React, { useState, useContext } from 'react'

//親コンポーネント

//useContextの初期値を設定。
const CountContext = React.createContext({} as {
  count: number
  setCount: React.Dispatch<React.SetStateAction<number>>
})

const Parent: React.FC = () => {
    const [count, setCount] = useState(0)
    return (
        <>
          //孫コンポーネントを含む子コンポーネントをuseContextで定めた変数で囲む。
          //valueでcountとsetCountをオブジェクトで渡している点に注意
          <CountContext.Provider value={{ count, setCount }}>
           <Child />
          </CountContext.Provider >
        </>
    )
}
 
//子コンポーネント
//特に変更なし
const Child: React.FC = () => {
    return (
        <>
         <GrandChild />
        </>
    )
}

//孫コンポーネント
const GrandChild: React.FC = () => {
    // 親要素で指定した変数を受け取る
    const {count, setCount} = useContext(CountContext)
    return (
        <>
         //親要素のuseStateがそのまま使える!
         <button onClick={() => setCount(count + 1)}>+</button>
         <button onClick={() => setCount(count - 1)}>-</button>
        </>
    )
}

ここで大事なのは親コンポーネントの


const CountContext = React.createContext({} as {
  count: number
  setCount: React.Dispatch<React.SetStateAction<number>>
})

の部分です。

jsでは

const CountContext = React.createContext()

と、初期値を特に設定しなくてもいいようですが、(いい意味で)型に厳しいTypeScriptでは初期値を必ず設定してあげる必要があるようです。

##補足
もし親コンポーネントと孫コンポーネントが別ファイル(というか、その状況の方が多い気もするけど)の場合、

  • 親要素のuseContextで設定した変数にexportをつけてあげる
  • 孫要素でその変数をインポートする

必要があります。

【親コンポーネント】

Patrent.tsx
import React, { useState } from 'react'

//useContextの初期値を設定。
export const CountContext = React.createContext({} as {
  count: number
  setCount: React.Dispatch<React.SetStateAction<number>>
})

const Parent: React.FC = () => {
    const [count, setCount] = useState(0)
    return (
        <>
          //孫コンポーネントを含む子コンポーネントをuseContextで定めた変数で囲む。
          //valueでcountとsetCountをオブジェクトで渡している点に注意
          <CountContext.Provider value={{ count, setCount }}>
           <Child />
          </CountContext.Provider >
        </>
    )
}

【子コンポーネント】

Child.tsx
//特に変更なし
const Child: React.FC = () => {
    return (
        <>
         <GrandChild />
        </>
    )
}

【孫コンポーネント】

GrandChild.tsx

import { CountContext } from './Parent.tsx'
const GrandChild: React.FC = () => {
    // 親要素で指定した変数を受け取る
    const {count, setCount} = useContext(CountContext)
    return (
        <>
         //親要素のuseStateがそのまま使える!
         <button onClick={() => setCount(count + 1)}>+</button>
         <button onClick={() => setCount(count - 1)}>-</button>
        </>
    )
}

最後に

propsのバケツリレーがなくなったので、改修がかなりしやすくなりましたね。
ただuseContextは今回で言う親コンポーネントと孫コンポーネントの依存性をバリバリに強くしてしまうので、再利用性がやや失われてしまいます。

ご利用は計画的に🙏

28
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?