【React】useContextの使い方 💡

# useContextとは

React Hooksの一つで、グローバルにデータを管理することができる仕組みです。
通常親コンポーネントから子コンポーネントにデータを渡す際は`props`を介して行うかと思います。
しかし親から子、そのまた子といったように複数のコンポーネントを介してデータを渡す場合にpropsでのバケツリレーのようなやり方では設定が複雑になってきます。
`useContext`を使用することで`props`を利用することなく異なる階層のコンポーネントとデータの共有を行うことができます。

:::note
グローバルに「データ」を管理することができるもの。
「props」によるバケツリレーを防ぐために利用する。
:::

## シンプルなコード例

下記のようなディレクトリ構造でuseContextを使用してみます。

├ Example.js
├ components/
　　　├ Child.js
　　　├ GrandChild.js

```Example.jsx
import Child from "./components/Child";
import { createContext } from "react";
export const MyContext = createContext("useContextのテスト");
const Example = () => {
  return <Child />;
};
export default Example;
```

```Child.jsx
import GrandChild from "./GrandChild";

const Child = () => (
  <div style={{ border: "1px solid black", padding: 10 }}>
    <h3>子コンポーネント</h3>
    <GrandChild />
  </div>
);

export default Child;
```

```GrandChild.jsx
import { useContext } from "react";
import { MyContext } from "../Example";
const GrandChild = () => {
  const value = useContext(MyContext);
  return (
    <div style={{ border: "1px solid black" }}>
      <h3>孫コンポーネント</h3>
      {value}
    </div>
  );
};
export default GrandChild;
```


上記関数コンポーネントを実行していただくと下記のように`props`を使用することなく、孫コンポーネントにデータを渡せていることがわかるかと思います。
![スクリーンショット 2023-01-16 19.12.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/21f5a200-b50b-5c21-c5da-80a4436fd190.png)

### コード解説

`useContext`を使用するには、reactから`createContext()`を読み込む必要があります。

```Example.js
import { createContext } from "react";
```

その`createContext()`を使用し、コンテキストを定義し、`export`します。
`createContext()`の引数には`props`を通さずに渡したい値を記述します。
```Example.js
export const MyContext = createContext("useContextのテスト");
```

そして`GrandChild`コンポーネントで使用したいので、`import`しています。
ここで`useContext()`を`react`から読み込みます。

```GrandChild.js
import { useContext } from "react";
import { MyContext } from "../Example";
```

最後に`useContext()`の引数に`import`したMyContextを渡すことで、設定しておいた値を取得することができます。

```GrandChild.js
const value = useContext(MyContext);
// value = useContextのテスト
```

## stateとuseContextを組み合わせて使う方法

次にネスト構造になっていない、コンポーネント間でクリックを検知して、`state`を変更する方法について解説します。
下記のような構造で作成します。

├ Example.js
├ components/
　　　├ Child.js
　　　├ GrandChild.js
　　　├ Other.js

Otherコンポーネントの兄弟に、Childコンポーネントがあり、そのChildコンポーネントの中にはGrandChildコンポーネントがあるような構造です。
Otherコンポーネントのクリックを検知し、GrandChirdにある`state`を変更させます。

```Example.jsx
import { createContext, useState } from "react";
import Child from "./components/Child";
import OtherChild from "./components/OtherChild";
export const MyContext = createContext();

const Example = () => {
  const [value, setValue] = useState(0);
  return (
    <MyContext.Provider value={[value, setValue]}>
      <Child />
      <OtherChild />
    </MyContext.Provider>
  );
};

export default Example;
```

```Other.jsx
import { useContext } from "react";
import { MyContext } from "../Example";

const OtherChild = () => {
  const [, setValue] = useContext(MyContext);
  const clickHandler = (e) => {
    setValue((prev) => prev + 1);
  };

  return (
    <div>
      <h3>他の子コンポーネント</h3>
      <button onClick={clickHandler}>+</button>
    </div>
  );
};

export default OtherChild;
```
```Child.jsx
import GrandChild from "./GrandChild";
const Child = () => (
  <div style={{ border: "1px solid black", padding: 10 }}>
    <h3>子コンポーネント</h3>
    <GrandChild />
  </div>
);
export default Child;
```
```GrandChild.jsx
import { useContext } from "react";
import { MyContext } from "../Example";
const GrandChild = () => {
  const [value] = useContext(MyContext);
  return (
    <div style={{ border: "1px solid black" }}>
      <h3>孫コンポーネント</h3>
      {value}
    </div>
  );
};
export default GrandChild;
```

上記コンポーネントを実行すると、下記のように他のコンポーネントで作成したボタンをクリックすると、孫コンポーネントにある`state(カウント)`が変化するようになります。

![スクリーンショット 2023-01-18 20.52.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/47a1d584-e51c-581c-9f19-12a6c2fa71fe.png)

### コード解説

まず、`state`を使用するコンポーネントを全て読み込んでいる場所で`useState`や`context`を定義します。


```Example.js
import { createContext, useState } from "react";
import Child from "./components/Child";
import OtherChild from "./components/OtherChild";
export const MyContext = createContext(); // context定義

const Example = () => {
  const [value, setValue] = useState(0); // state定義
  ...
};
```

定義した`context`(MyContext)の**Provider**というコンポーネントでstateを使用するコンポーネントを包装します。
その`Provider`コンポーネントの`value`を設定することで、`value`にセットされた値が`useContext`を通してネストされたコンポーネントで取得することができるようになります。
今回は`value`に設定する値は定義しておいた、stateとその変更関数ごと、配列のまま渡します。

```Example.jsx
<MyContext.Provider value={[value, setValue]}> 
  <Child />
  <OtherChild />
</MyContext.Provider>
```
:::note
useContextの挙動とすると順々に親のコンポーネントを参照していき、`Provider`コンポーネントの`value`に設定された値を取得します。
`Provider`コンポーネントの`value`を設定しなかった場合、`createContext()`時に渡された引数を取得します。
例)
`export const MyContext = createContext('これが初期の値になるよ');`
:::

ボタンが作成されているOther.jsでは`context`をimportし、`useContext()`の引数に渡します。

```Other.jsx
import { useContext } from "react"; 
import { MyContext } from "../Example"; // 定義したMyContextのimport
```

それを`useContext()`の引数に渡し、返り値を格納することで`value`に設定された値を取得できます。
`value`には`[value, setValue]`の配列を渡してありますが、このコンポーネントでは`state`は使いませんので、分割代入で1番目の要素である`setValue`、つまりstateの変更関数だけを取得します。

```Other.js
 const [, setValue] = useContext(MyContext); // 使用する変更関数のみ取得
```

そしてボタンのonClickイベントで`clickHandler`を実行させます。
`clickHandler()`は`setValue`で`state`の値に+1をする関数です。

```Other.js
 const clickHandler = (e) => {
    setValue((prev) => prev + 1);
  };
```

stateを表示しているGrandChild.jsも同じ要領で、`useContext()`の引数に`context`、つまり`value`に設定した`state`の値のみ取得して表示させているという流れです。

```GrandChild.jsx
import { useContext } from "react";
import { MyContext } from "../Example"; // contextのimport
const GrandChild = () => {
  const [value] = useContext(MyContext); // 分割代入でstateのみ取得
  return (
    <div style={{ border: "1px solid black" }}>
      <h3>孫コンポーネント</h3>
      {value}
    </div>
  );
};
export default GrandChild;
```