next.jsでReactのContextを利用する時の心得

複数ページ間にデータを共有した時や、深いコンポーネント階層構造のpropsバケツリレイ問題を解消したい時とか、ReactのContextを利用する手段がある。基本グローバルだが、Providerの括る範囲により局所に制限されることができる。

この記事ではnext.jsでReactのContextを利用する時の心得をメモする。

### サンプルのソースコード

早速ソースコードを記載して、心得は全てのこれをベースにしている。

```tsx
'use client';

import {
  createContext,
  useState,
  useContext,
  type ReactNode,
  type Dispatch,
  type SetStateAction,
} from 'react';

export type Hoge = {
  id?: number;
  name: string;
  values: string[];
};

const HogeContext = createContext<Hoge | undefined>(undefined);
const HogeUpdateContext = createContext<Dispatch<SetStateAction<Hoge>> | undefined>(undefined);

type Props = {
  children: ReactNode;
};

export function HogeProvider({ children }: Props) {
  const [searchCondition, setHoge] = useState<Hoge>({
    id: undefined,
    name: '',
    values: [],
  });

  return (
    <HogeContext.Provider value={searchCondition}>
      <HogeUpdateContext.Provider value={setHoge}>{children}</HogeUpdateContext.Provider>
    </HogeContext.Provider>
  );
}

export const useHoge = () => {
  const context = useContext(HogeContext);
  if (context === undefined) {
    throw new Error('useHoge must be used within a HogeProvider');
  }
  return context;
};

export const useHogeUpdate = () => {
  const context = useContext(HogeUpdateContext);
  if (context === undefined) {
    throw new Error('useHogeUpdate must be used within a HogeProvider');
  }
  return context;
};

```

### 心得1：Context利用のため、next.jsの`'use client';`をContextのソースコードファイルに記載する

next.jsのapp routerを利用する場合、RCC(React Client Component)しかContextを利用できない。RSC(React Server Component)からContextのProviderを利用する可能性があるので、Context定義ファイルに`'use client';`を付けた方が無難。

### 心得2：valueとvalueを更新する関数は別々のContextにする

[この](https://zenn.dev/yuta_ura/articles/react-context-api#%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-1.-%E5%80%A4%E6%9C%AC%E4%BD%93%E3%81%A8%E5%80%A4%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92-1-%E3%81%AE-context-%E3%81%AB%E5%85%A5%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B)アンチパターン記事に書いた通り、`{hoge, setHoge}`オブジェクトを１つのContextに纏める場合、片方が変わったら、新たのオブジェクトが生成されるため、hogeとsetHogeの利用されるところが全て再描画されてしまう。

setHogeが基本不変なものであり、hogeと別のContextに設定する場合は、お互いに影響せず、パフォーマンスの悪い影響が避けられる。

```tsx
  return (
    <HogeContext.Provider value={searchCondition}>
      <HogeUpdateContext.Provider value={setHoge}>{children}</HogeUpdateContext.Provider>
    </HogeContext.Provider>
  );
```

### 心得3：繋ぎ込みも同じファイルにする

- ProviderとContext定義を１つのファイルの中に纏める
- Context自体を公開せず、代わりにContext値を取得するHooksを定義する

### 心得4：初期値をundefinedにして、Hooks中にundefinedチェックを行なう

HogeProviderで括るコンポーネント中からしかuseHogeやuseHogeUpdateを利用できないが、実装時に忘れていた場合、ちゃんとErrorを出すようにして、正しい使い方を保証する。

```tsx
export const useHoge = () => {
  const context = useContext(HogeContext);
  if (context === undefined) {
    throw new Error('useHoge must be used within a HogeProvider');
  }
  return context;
};

export const useHogeUpdate = () => {
  const context = useContext(HogeUpdateContext);
  if (context === undefined) {
    throw new Error('useHogeUpdate must be used within a HogeProvider');
  }
  return context;
};

```

### 心得5：stateとreducerの使い分け

- 上記サンプルソースコードのような単純な丸ごとのデータ更新の場合はstateを利用する
- データに対して追加、削除、更新みたい色んな操作がある場合はreducerを利用する


### 参考記事

- [コンテクストで深くデータを受け渡す](https://ja.react.dev/learn/passing-data-deeply-with-context)
- [useContext](https://ja.react.dev/reference/react/useContext#updating-data-passed-via-context)
- [リデューサとコンテクストでスケールアップ](https://ja.react.dev/learn/scaling-up-with-reducer-and-context#recap)
- [正しく使う ReactContext](https://zenn.dev/yuta_ura/articles/react-context-api#%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-1.-%E5%80%A4%E6%9C%AC%E4%BD%93%E3%81%A8%E5%80%A4%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92-1-%E3%81%AE-context-%E3%81%AB%E5%85%A5%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B)
- [How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively#conclusion)