
実装

React Contextで管理していたキャラクター選択と言語設定をzustandに移行しました。Contextの問題点(Provider地獄、不要な再レンダリング)を解消しつつ、localStorageへの永続化も追加しています。
React Context → zustandの具体的な移行手順
persist ミドルウェアでlocalStorage永続化する方法
レガシー値のマイグレーション(merge関数)パターン
Provider削除によるlayout.tsxの簡素化
// contexts/CharacterContext.tsx
const CharacterContext = createContext<CharacterContextType>(...);
export function CharacterProvider({ children }) {
const [selected, setSelected] = useState("akari");
// localStorage読み込み、レガシー値変換...
return (
<CharacterContext.Provider value={{...}}>
{children}
</CharacterContext.Provider>
);
}
// layout.tsx — Provider地獄
<AuthSessionProvider>
<LanguageProvider>
<CharacterProvider>
{children}
</CharacterProvider>
</LanguageProvider>
</AuthSessionProvider>
// stores/useCharacterStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface CharacterStore {
selectedSlug: string;
setSelectedSlug: (slug: string) => void;
}
const LEGACY_SLUG_MAP: Record<string, string> = {
blue: "haruto",
red: "akari",
};
export const useCharacterStore = create<CharacterStore>()(
persist(
(set) => ({
selectedSlug: "akari",
setSelectedSlug: (slug) => set({ selectedSlug: slug }),
}),
{
name: "selectedCharacter", // localStorageキー
merge: (persisted, current) => {
const p = persisted as Partial<CharacterStore> | undefined;
const raw = p?.selectedSlug ?? current.selectedSlug;
return { ...current, selectedSlug: LEGACY_SLUG_MAP[raw] ?? raw };
},
},
),
);
persist の merge オプションは、localStorageから読み込んだ値とデフォルト値をマージする関数です。ここでレガシー値の変換を行うことで、既存ユーザーのデータを壊さずに移行できます。
// Before: Provider地獄
<AuthSessionProvider>
<LanguageProvider>
<CharacterProvider>{children}</CharacterProvider>
</LanguageProvider>
</AuthSessionProvider>
// After: Providerが不要に
<AuthSessionProvider>
{children}
</AuthSessionProvider>
zustand storeファイルを作成(persist設定、レガシーマイグレーション含む)
各コンポーネントのimportをuseContext → useXxxStore に置換
layout.tsxからProvider wrapperを削除
旧Contextファイルを削除
ビルド・動作確認(localStorage既存値の互換性確認含む)
zustandはProvider不要で、import して使うだけ → Provider地獄解消
persist ミドルウェアで localStorage永続化がワンライナー
merge関数でレガシー値のマイグレーションを宣言的に記述できる
storeにはUIに必要な最小限のstate(slug等)だけ持ち、派生データはhookで計算する