ReactHooksについてまとめる（Context Hooks）

# 概要
Contextはコンポーネントが親コンポーネントから情報を受け取る際に、それをpropsとして渡さずに済むようにします。
アプリのトップレベルのコンポーネントはネストの数に関係なくその下にある全てのコンポーネントに対してprops経由なしに情報を渡せる。


## useContext
`Contex`tを使用するためには、その値を使用したいツリー全体の親コンポーネントを対応する`Context.Provider`でラップします。
以下の例では`Buttonコンポーネント`内で`theme`としての値を使用したい場合に、`Buttonコンポーネント`の親コンポーネントである`Formコンポーネント`を`ThemeContext.Provider`でラップする。

```jsx
function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  return (
    // ...
    <Button />
    // ...
  )
}
```

そして、`useContext`を使用することによってその値をとして　"dark"を受け取ることができます。ここで`Buttonコンポーネント`がProviderとの間に何層ネストされてても関係なく`useContext`の値は取得することができる。

```jsx
import { useContext } from 'react';

function Button() {
  const theme = useContext(ThemeContext);
  // ...
```


もし`Provider`を親ツリーから見つけられない場合は、`useContext`が返す値は、その`Context`を作成した時に指定したデフォルト値になる。
```
const ThemeContext = createContext(null);
```

useContextを使った例

https://codesandbox.io/embed/magical-rain-3vi5em?fontsize=14&hidenavigation=1&theme=dark

codesandboxの例のように、contextを更新したい場合は、setStateをProvider配下のコンポーネントに渡してイベントハンドラー等で発火させてstateの値を更新する。 Providerの引数に `value={{ theme, setTheme }}` のように渡して`const {setTheme} = useContext(ThemeContext)`のように受け取りstateを更新する関数を受け取ることもできる。

https://codesandbox.io/embed/flamboyant-sunset-meu1t0?fontsize=14&hidenavigation=1&theme=dark


### 大規模アプリでのContext
#### ContextとReducer
大規模なアプリでは、contextとreducerを組み合わせて、ある状態に関連するロジックをコンポーネントから抽出することが多い。
こうすることによって、contextの更新に関連するロジックをコンポーネントから分離でき単体テスト等が書きやすくメンテナンスしやすいコードが書ける。

https://codesandbox.io/embed/flamboyant-jackson-tx4fdl?fontsize=14&hidenavigation=1&theme=dark


#### レンダリングの最適化

ログインの認証状態等を管理するために最上位の親コンポーネントでProviderでラップすることが多いが、その際にページ移動のような遷移だけで login関数が別オブジェクトとして認識されるために、再レンダリングが走り`useContext(AuthContext)`を利用する深い子コンポーネントまで再レンダリングしてしまう。

```jsx
function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}
```

再レンダリングの必要はないため、別オブジェクトとして認識されないように　`useCallback`でlogin関数をラップして、オブジェクトの作成を `useMemo`でvalueとして渡している `{currentUser,login}`を`useMemo`でラップすることでパフォーマンスが向上できる。

こうすることによって、`currentUser`が変更されない限り、`useContext(AuthProvider)`を呼び出すコンポーネントは再レンダリングの必要がなくなる。

```jsx
import { useCallback, useMemo } from 'react';

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}
```

### その他の使い方
同じ名前のProviderでラップすることでcontextの値をOverrideすることができる。


```jsx
<ThemeContext.Provider value="dark">
  ...
  <ThemeContext.Provider value="light">
    <Footer /> // ← この中での const theme = useContext(ThemeContext); のthemeの値は "light"
  </ThemeContext.Provider>
  ...
</ThemeContext.Provider>
```


# 参考

https://beta.reactjs.org/apis/react/useContext