複数ページ間にデータを共有した時や、深いコンポーネント階層構造のpropsバケツリレイ問題を解消したい時とか、ReactのContextを利用する手段がある。基本グローバルだが、Providerの括る範囲により局所に制限されることができる。
この記事ではnext.jsでReactのContextを利用する時の心得をメモする。
サンプルのソースコード
早速ソースコードを記載して、心得は全てのこれをベースにしている。
'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にする
このアンチパターン記事に書いた通り、{hoge, setHoge}
オブジェクトを1つのContextに纏める場合、片方が変わったら、新たのオブジェクトが生成されるため、hogeとsetHogeの利用されるところが全て再描画されてしまう。
setHogeが基本不変なものであり、hogeと別のContextに設定する場合は、お互いに影響せず、パフォーマンスの悪い影響が避けられる。
return (
<HogeContext.Provider value={searchCondition}>
<HogeUpdateContext.Provider value={setHoge}>{children}</HogeUpdateContext.Provider>
</HogeContext.Provider>
);
心得3:繋ぎ込みも同じファイルにする
- ProviderとContext定義を1つのファイルの中に纏める
- Context自体を公開せず、代わりにContext値を取得するHooksを定義する
心得4:初期値をundefinedにして、Hooks中にundefinedチェックを行なう
HogeProviderで括るコンポーネント中からしかuseHogeやuseHogeUpdateを利用できないが、実装時に忘れていた場合、ちゃんとErrorを出すようにして、正しい使い方を保証する。
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を利用する