# Next.js State Management Standard

> **Scope:** frontend/nextjs/state-management
> **Layer:** 2 (on keyword)
> **Keywords:** state, zustand, context, redux, client state, server state, useState
> **Load When:** deciding how to manage state in Next.js

**Verified against:** Next.js 15 + TanStack Query 5 + Zustand 5. Last-verified: 2026-05-20.

---

Server state (API data) lives in TanStack Query. UI state lives in React. No global state library unless genuinely needed.

## Core Rules

- ALWAYS use TanStack Query for anything that comes from the .NET API
- ALWAYS use `useState` for local UI state (modal open, selected tab, form step)
- NEVER install Zustand or Redux without first exhausting Server Components + TanStack Query
- NEVER use React Context for server state — Context does not cache or deduplicate
- ALWAYS derive state from server data rather than syncing it to local state

## State Decision Tree

```
Is this data from the .NET API?
  YES → TanStack Query (useQuery / useMutation)

Is this local UI state (open/closed, selected, current step)?
  YES → useState in the component that needs it

Does multiple components need this UI state?
  YES — is it parent-child?     → prop drilling (2-3 levels is fine)
  YES — is it truly global?     → React Context (theme, auth user, locale)
  YES — complex with many actions? → Consider Zustand (last resort)
```

## Server State (TanStack Query)

```ts
// GOOD — server state in TanStack Query
const { data: users } = useUsers(); // cached, deduplicated, background-refetched

// BAD — copying server state to useState
const [users, setUsers] = useState([]);
useEffect(() => { fetchUsers().then(setUsers); }, []);
```

## Local UI State (useState)

```tsx
function UserList() {
  const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
  const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
}
```

## When Zustand Is Justified

Only add Zustand if ALL of these are true:
1. State is needed in 5+ unrelated components
2. State has complex update logic (not just toggle/set)
3. Context causes measurable re-render performance issues
4. State is not from the server

## Common Mistakes

| Wrong | Right | Why |
|-------|-------|-----|
| `useEffect` + `useState` for API data | TanStack Query | Stale data, no cache, double-render |
| Global store for user list | `useUsers()` | TanStack Query handles sync/cache |
| Context for everything | useState + prop drilling first | Context causes all consumers to re-render |
| Zustand by default | Start with useState | YAGNI — most state is local |

---

*MORPH-SPEC by Polymorphism Tech*
