クライアント状態管理スキル
クライアントサイドの状態管理の全ワークフローを提供します。
いつ使うか
このスキルは以下の場合に使用してください:
- グローバルUI状態の管理(サイドバー、テーマ)
- URL状態の管理(ページネーション、フィルタ、検索、タブ)
- Contextプロバイダーを避けたい場合
- 状態をURLに永続化したい場合
manage-swr-data との使い分け
manage-client-state(このスキル): クライアント内で完結する UI 状態
- サイドバー開閉、テーマ、モーダル表示
- URL 同期(ページネーション、検索、フィルタ)
- ブラウザストレージ(localStorage)
manage-swr-data: サーバーから取得するデータ
- API レスポンスのキャッシュ
- データ取得・変更(GET/POST/PUT/DELETE)
- バックグラウンド再検証
判断基準: サーバーから取得するデータは manage-swr-data、クライアント内で完結する状態は manage-client-state を使用します。
状態管理の選択
| 要件 | 手段 | 例 |
|---|---|---|
| URL復元(戻る/共有) | nuqs | page, query, filter |
| localStorage永続化 | useSWRImmutable + useEffect | theme, settings |
| セッション内のみ | useSWRImmutable | modal, selectedRows |
ワークフロー
1. グローバル状態(useSWRImmutable)
typescript1'use client' 2 3import useSWRImmutable from 'swr/immutable' 4 5export function useSidebar() { 6 const { data, mutate } = useSWRImmutable('sidebar-open', null, { 7 fallbackData: true 8 }) 9 10 return { 11 isOpen: data ?? true, 12 toggle: () => mutate(!data, false) 13 } 14} 15 16// コンポーネントでの使用 17export function Sidebar() { 18 const { isOpen, toggle } = useSidebar() 19 20 return ( 21 <aside className={isOpen ? 'open' : 'closed'}> 22 <button onClick={toggle}>切り替え</button> 23 </aside> 24 ) 25}
2. URL状態(nuqs)
Server Component(パース)
typescript1import { createSearchParamsCache, parseAsInteger } from 'nuqs/server' 2import type { PageProps } from 'next/types' 3 4export const searchParamsCache = createSearchParamsCache({ 5 page: parseAsInteger.withDefault(1), 6}) 7 8export default async function Page({ searchParams }: PageProps<'/users'>) { 9 const { page } = await searchParamsCache.parse(searchParams) 10 11 const users = await fetchUsers({ page }) 12 13 return <UserList users={users} page={page} /> 14}
Client Component(状態)
typescript1'use client' 2 3import { useQueryStates, parseAsInteger, parseAsString } from 'nuqs' 4 5export function UserFilters() { 6 const [{ page, query }, setFilters] = useQueryStates({ 7 page: parseAsInteger.withDefault(1), 8 query: parseAsString.withDefault(''), 9 }) 10 11 return ( 12 <input 13 value={query} 14 onChange={(e) => setFilters({ query: e.target.value, page: 1 })} 15 /> 16 ) 17}
重要ルール
グローバル状態
useSWRImmutableを使用(useSWRではない)- デフォルト値に
fallbackDataを設定 - 再検証なしで更新するには
mutate(newValue, false)を使用 - サーバーデータには使用しない
URL状態
- Server Componentでは
createSearchParamsCacheを使用 - Client Componentでは
useQueryStatesを使用 - パーサー(
parseAsInteger、parseAsStringなど)を使用 - 常に
.withDefault()でデフォルト値を設定
詳細パターン
詳細な実装パターンについては references を参照してください:
- global-state.md - グローバルUI状態、localStorage、型安全
- url-state.md - URLパラメータ、カスタムパーサー、配列状態
- integration-pattern.md - Server/Client統合、フィルタ実装、複数状態の併用