# State Management

> When to activate: Zustand, Jotai, XState, TanStack Query, URL state, Redux, state management, client state, server state

- Skill: `mattakushi432/state-management` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/state-management`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/state-management/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/state-management

---

# State Management Patterns

## State Categories

| Concern | Tool |
|---------|------|
| Server/remote state | TanStack Query, SWR |
| Client UI state | Zustand, Jotai |
| URL/shareable state | search params, nuqs |
| Form state | React Hook Form |
| Complex flows | XState |
| Global derived state | Jotai atoms |

## TanStack Query (Server State)

```tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetch with caching
function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
    staleTime: 5 * 60 * 1000, // 5 min
  });
}

// Optimistic mutation
function useUpdateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (user) => fetch(`/api/users/${user.id}`, { method: 'PUT', body: JSON.stringify(user) }),
    onMutate: async (updated) => {
      await qc.cancelQueries({ queryKey: ['users'] });
      const previous = qc.getQueryData(['users']);
      qc.setQueryData(['users'], (old) => old.map(u => u.id === updated.id ? updated : u));
      return { previous };
    },
    onError: (_, __, ctx) => qc.setQueryData(['users'], ctx.previous),
    onSettled: () => qc.invalidateQueries({ queryKey: ['users'] }),
  });
}
```

## Zustand (Client State)

```ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface CartStore {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
  clear: () => void;
  total: () => number;
}

export const useCartStore = create<CartStore>()(
  devtools(persist(immer((set, get) => ({
    items: [],
    addItem: (item) => set(state => {
      const existing = state.items.find(i => i.id === item.id);
      if (existing) existing.qty += 1;
      else state.items.push({ ...item, qty: 1 });
    }),
    removeItem: (id) => set(state => {
      state.items = state.items.filter(i => i.id !== id);
    }),
    clear: () => set({ items: [] }),
    total: () => get().items.reduce((sum, i) => sum + i.price * i.qty, 0),
  })), { name: 'cart' }))
);
```

## Jotai (Atomic State)

```ts
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// Primitive atom
const countAtom = atom(0);

// Derived atom (computed)
const doubleAtom = atom(get => get(countAtom) * 2);

// Writable derived
const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light');

// Async atom
const userAtom = atom(async () => {
  const res = await fetch('/api/me');
  return res.json();
});

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const double = useAtomValue(doubleAtom);
  return <button onClick={() => setCount(c => c + 1)}>{count} (×2: {double})</button>;
}
```

## URL State with nuqs

```tsx
import { useQueryState, parseAsInteger, parseAsString } from 'nuqs';

function ProductList() {
  const [page, setPage]     = useQueryState('page', parseAsInteger.withDefault(1));
  const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''));
  const [sort, setSort]     = useQueryState('sort', parseAsString.withDefault('name'));

  // State is in URL: /products?page=2&q=phone&sort=price
  // Shareable, back-button safe, SSR-compatible
}
```

## XState (Complex Flows)

```ts
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

const checkoutMachine = createMachine({
  id: 'checkout',
  initial: 'cart',
  context: { items: [], error: null },
  states: {
    cart:     { on: { CHECKOUT: 'shipping' } },
    shipping: { on: { BACK: 'cart', NEXT: 'payment' } },
    payment:  {
      on: { BACK: 'shipping', SUBMIT: 'processing' }
    },
    processing: {
      invoke: { src: 'submitOrder',
        onDone:  { target: 'success' },
        onError: { target: 'payment', actions: assign({ error: (_, e) => e.data.message }) }
      }
    },
    success: { type: 'final' },
  }
});

function Checkout() {
  const [state, send] = useMachine(checkoutMachine, {
    actors: { submitOrder: (ctx) => submitOrderAPI(ctx.items) }
  });
  return state.matches('cart') ? <Cart onNext={() => send('CHECKOUT')} /> : null;
}
```

