【React】グローバルなState管理をする　React.createContext　Context.Provider

# はじめに
Reactでコンポーネントが多くなると、Propsを使って値の受け渡しが多くなり、ルートのコンポーネントから最下層のコンポーネントへPropsのバケツリレーのようになることがあります。
そこでReact.createContextを使うと、グルーバルなState管理ができるようになり、コードがスッキリし、複雑化するのを防ぐことができるようになります。
React.createContextの使い方を[モダンJavaScriptの基本から始める React実践の教科書](https://www.amazon.co.jp/%E3%83%A2%E3%83%80%E3%83%B3JavaScript%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%82%8B-React%E5%AE%9F%E8%B7%B5%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%E6%9C%80%E6%96%B0ReactHooks%E5%AF%BE%E5%BF%9C-Informatics-IDEA/dp/481561072X)で勉強したことを参考にまとめていきます。

https://ja.reactjs.org/docs/context.html

## １．React.createContextでContextの器を作る

はじめにcomponentの下にprovidersというフォルダを作成し、グローバルなState管理をするためのファイルを作成します。ここでは、```AdminFlagProvider.jsx```を作成しました。

```react:react-basic/src/components/providers/AdminFlagProvider.jsx
import { createContext } from "react";

export const AdminFlagContext = createContext({});
```

## ２．作成したContextのProviderでグローバルStateを扱いたいコンポーネントを囲う

Providerのコンポーネントは、何でも囲めるように```Propsとしてchildrenを受け取るようにする```のがポイントです。
上記で用意したAdminFlagContextには、Providerが用意されているので、それを返却するようにします。


```react:react-basic/src/components/providers/AdminFlagProvider.jsx
import { createContext } from "react";

export const AdminFlagContext = createContext({});

export const AdminFlagProvider = (props) => {
  // 何でも囲めるようにPropsとしてchildrenを受け取るようにする。
  const { children } = props;

  const sampleobj = { sampleValue: "テスト" };

  return (
    // AdminFlagContextには、Providerが用意されているのを返却する。
    // valueの中にグローバルに扱う実際の値を設定する
    <AdminFlagContext.Provider value={sampleobj}>
      {children}
    </AdminFlagContext.Provider>
  );
};
```

## ３．Providerを使いたい範囲のコンポーネントを囲う

今回は、アプリ全体で参照できるようにするために、index.jsの中でAppコンポーネントを囲みます。

```react:/react-basic/src/index.js
import ReactDom from "react-dom";
import { App } from "./App";
import "./index.css";
import { AdminFlagProvider } from "./components/providers/AdminFlagProvider";

ReactDom.render(
  <AdminFlagProvider>
    <App />
  </AdminFlagProvider>,
  document.getElementById("root")
);
```

## ４．Stateを参照したいコンポーネントでReact.useContextを使う

React.useContextを使い、その引数に対象のContextを指定するだけで参照することができます。下記のコードを実行することで、AdminFlagProvider.jsxで定義したsampleobjの値を習得することができます。

```react:react-basic/src/components/EditButton.jsx
import { useContext } from "react";

import { AdminFlagContext } from "./providers/AdminFlagProvider";

export const EditButton = (props) => {
  const AdminFlagValue = useContext(AdminFlagContext);
  const { isAdmin } = props;

// useContextで用意したsampleobjの値を取得することができた
  console.log(AdminFlagValue);

  return <button disabled={!isAdmin}>編集</button>;
};

```

```react:/react-basic/src/App.jsx
import { useState } from "react";
import { Card } from "./components/Card";

export const App = () => {
  const [isAdmin, setIsAdmin] = useState(false);
  const onClickSwitch = () => setIsAdmin(!isAdmin);

  return (
    <div>
      {isAdmin ? <span>管理者です</span> : <span>管理者以外です</span>}
      <br />
      <button onClick={onClickSwitch}>切り替えボタン</button>
      <Card isAdmin={isAdmin} />
    </div>
  );
};
```

```react:/react-basic/src/components/Card.jsx
import { EditButton } from "./EditButton";

export const Card = (props) => {
  const { isAdmin } = props;

  return (
    <div>
      {/* propsをそのまま受け渡ししている、バケツリレーを行っている */}
      <EditButton isAdmin={isAdmin} />
    </div>
  );
};
```

## 次のステップ！Contextの更新と参照処理を行う

ContextのStateの更新と参照には、Providerを記載しているファイルににStateを定義し、そのStateの値と更新関数をContextのvalueに設定します。

```react:/react-basic/src/components/providers/AdminFlagProvider.jsx
import { createContext, useState } from "react";

export const AdminFlagContext = createContext({});

export const AdminFlagProvider = (props) => {
  const { children } = props;

// Contextの更新と参照するためにStateを定義する
  const [isAdmin, setIsAdmin] = useState(false);

  // ContextオブジェクトとしてisAdminとsetIsAdminを設定(オブジェクトの省略記法)
  return (
    <AdminFlagContext.Provider value={{ isAdmin, setIsAdmin }}>
      {children}
    </AdminFlagContext.Provider>
  );
};
```

useContextを使って、グローバルなStateの値を取得することができるようになりました。
さらに、propsで値を受け渡していた箇所を削除することができ、スッキリさせることができます。


```react:react-basic/src/components/EditButton.jsx
import { useContext } from "react";

import { AdminFlagContext } from "./providers/AdminFlagProvider";

// 不要なpropsを削除できた
export const EditButton = () => {

  //useContextを使って、グローバルなStateの値を取得する
  const { isAdmin } = useContext(AdminFlagContext);

  return <button disabled={!isAdmin}>編集</button>;
};

```

```react:/react-basic/src/components/Card.jsx
import { EditButton } from "./EditButton";

// 不要なpropsを削除でき、シンプルになりました。
export const Card = () => {
  return (
    <div>
      <EditButton />
    </div>
  );
};
```

```react:/react-basic/src/App.jsx
import { useContext } from "react";
import { Card } from "./components/Card";
import { AdminFlagContext } from "./components/providers/AdminFlagProvider";

export const App = () => {
// useContextから更新するための、Stateを取得を行う。
  const { isAdmin, setIsAdmin } = useContext(AdminFlagContext);

//グローバルなStateの値を更新！
  const onClickSwitch = () => setIsAdmin(!isAdmin);

  return (
    <div>
      {isAdmin ? <span>管理者です</span> : <span>管理者以外です</span>}
      <br />
      <button onClick={onClickSwitch}>切り替えボタン</button>
      <Card isAdmin={isAdmin} />
    </div>
  );
};
```