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

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

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

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

##前提条件

```typescript
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を孫コンポーネントに送ってしまいます。

```typescript
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>
        </>
    )
}
```

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

```typescript

const CountContext = React.createContext({} as {
  count: number
  setCount: React.Dispatch<React.SetStateAction<number>>
})
```
の部分です。

jsでは

```js
const CountContext = React.createContext()
```
と、初期値を特に設定しなくてもいいようですが、（いい意味で）型に厳しい**TypeScriptでは初期値を必ず設定してあげる必要がある**ようです。


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

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

必要があります。

【親コンポーネント】

```typescript: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 >
        </>
    )
}
```
【子コンポーネント】

```typescript:Child.tsx
//特に変更なし
const Child: React.FC = () => {
    return (
        <>
         <GrandChild />
        </>
    )
}
```
【孫コンポーネント】

```typescript: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は今回で言う親コンポーネントと孫コンポーネントの依存性をバリバリに強くしてしまうので、再利用性がやや失われてしまいます。

ご利用は計画的に🙏