【Nextjs on Vercel】useContextの使い方に気をつけよう

typescript + nextjsをvercelにデプロイしたところ、Reactの`useContext`まわりの扱いでエラーに苦しんだので書きます

# 環境
* MacOS - AppleM1チップ - 16GBメモリ - Sonoma14.5
* npm - `v10.8.0`
* React - `v^18.0`
* Nextjs - `v14.2.4`

# Vercel - 発生したエラー
Buildでこちら2つのエラーが発生しました。
```diff:Build Logs
- Error:
- x You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
```

```diff:Build Logs
- Failed to compile.
  app/page.tsx
- Type error: Page "app/page.tsx" does not match the required types of a Next.js Page.
- "Context" is not a valid Page export field.
```

ルートページの`tsx`は以下のようにしていました
```tsx:app/page.tsx
import { createContext, useState } from 'react';
import { Header, MainBody, ...} from '@components';
import { contextConfig, contextData } from 'context';

export const Context = createContext<contextConfig>({
    context_data: {
        // グローバルStateの変数の型
    } as contextData,
    setContextData: () => {}
});

export default function Home() {
    const [context_data, setContextData] = useState({
        // グローバルStateの変数の型
    } as contextData);

    return (
        <Context.Provider value={{context_data, setContextData}}>
            <Header />
            <MainBody>
                ...
            </MainBody>
        </Context.Provider>
    );
}
```

# エラー1: 解決策 - `use client`
### `"use client"`を追記する
* Nextjsでコンポーネント間の依存関係(`props`や`useContext`での変数の受け渡しなど)が発生する場合、クライアント側での実行に制御する必要があります
* `"use client"`が書かれたコンポーネントの子コンポーネントは**全て**クライアントで制御されることになるので、`"use server"`も駆使する場合は注意が必要

https://nextjs.org/docs/app/building-your-application/rendering#network-boundary

`createContext`の定義元で`"use client"`を追記してやる
```diff_tsx:app/page.tsx
+ "use client"
import { createContext, useState } from 'react';
import { Header, MainBody, ...} from '@components';
import { contextConfig, contextData } from 'context';

export const Context = createContext<contextConfig>({
...
```
これで1つ目のエラーは解消されました。

# エラー2: 解決策 - `Provider`
### ページコンポーネントとProviderは分ける
* Vercel環境でのNextjsは、`app`以下のルーティング専用のtsxは、ページコンポーネント以外の型の`export`を受け付けないようです
* `app/page.tsx`では、`Context`オブジェクトも`export`してしまっているので、エラーが発生していました

`Provier`コンポーネントとして分けてやる
```diff_tsx:provider.tsx
import { createContext, useState } from 'react';
import { contextConfig, contextData } from 'context';

export const Context = createContext<contextConfig>({
   context_data: {
        // グローバルStateの変数の型
    } as contextData,
   setContextData: () => {}
});

const Provider: React.FC<PropsWithChildren> = ({children}) => {
    const [context_data, setContextData] = useState({
        // グローバルStateの変数の型
   } as contextData);

   return (
       <Context.Provider value={{context_data, setContextData}}>
           {children}
       </Context.Provider>
   );
}

export default Provider;
```


```diff_tsx:app/page.tsx
import { Header, MainBody, ...} from '@components';
+ import Provider from '@components/provider';

export default function Home() {
    return (
+        <Provider>
            <Header />
            <MainBody>
                ...
            </MainBody>
+        </Provider>
    );
}
```
これで2つ目のエラーも解消されました

# 補足
### React - `useContext`
* グローバルStateを扱うhook
* `useState`は単一コンポーネント内でしか扱えず、子コンポーネントで使用する場合は`props`で渡す必要がある
* 深いネスト関係にあるコンポーネントでは、最上部から最下部に変数を渡したいとしても、ネストを辿って`props`で渡し続けるしかない
* `useContext`で作られた`<context.Provider />`を使えば、Providerの子コンポーネントはどの位置であっても変数を使用＆更新できる

```tsx:component.tsx
import { createContext, PropsWithChildren, useState } from 'react';

// グローバルStateの変数の型
type contextData = {
    prop1: string,
    prop2: number,
    prop3: string[]
}


// グローバルStateの変数と更新関数の型
type contextConfig = {
    context_data: contextData
    setContextData: React.Dispatch<React.SetStateAction<contextData>>
}

// Contextの作成
export const Context = createContext<contextConfig>({
    context_data: { // デフォルト値
        prop1: '',
        prop2: 0,
        prop3: []
    } as contextData,
    setContextData: () => {}
});

// Context.provider
const Provider: React.FC<PropsWithChildren> = ({children}) => {
    const [context_data, setContextData] = useState({
        prop1: '',
        prop2: 0,
        prop3: []
    } as contextData);

    return (
        <Context.Provider value={{context_data, setContextData}}>
            {children}
        </Context.Provider>
    );
}

export default Provider;
```