Decide where each piece of application state lives, so you don't end up with a global store that is 80% cached server responses.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add snoodleboot-io/prompticorn --skill minimal --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Minimal?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/snoodleboot-io-minimal-73ae8df7)More formats (shields.io, HTML) on the badges page.
# State Management Architecture (Minimal)
## Purpose
Decide where each piece of application state lives, so you don't end up with a global store that is 80% cached server responses.
## Core Techniques
### 1. Classify Before You Choose a Library
| Kind | Example | Lives in |
|---|---|---|
| Server cache | user list, order detail | React Query / SWR / RTK Query |
| URL | filters, page, selected tab | router search params |
| Form draft | in-progress inputs | form library, local |
| Local UI | dropdown open, hover | `useState` in the component |
| Shared client | theme, cart, undo stack | Zustand / Redux / Context |
Most "we regret Redux" stories are really "we put server data in Redux." Fetched
data is a *cache* — it needs staleness, revalidation, dedupe, and retries, none of
which a reducer gives you and all of which you will hand-roll badly.
### 2. Put Filters in the URL, Not in a Store
```ts
const [params, setParams] = useSearchParams();
const status = params.get("status") ?? "open";
```
URL state is free persistence, free sharing, free back-button support, and it
survives reload. State that a user would reasonably expect to bookmark belongs
there — anything else silently breaks link-sharing.
### 3. Derive; Don't Store Derived Values
```ts
// ❌ two sources of truth, guaranteed to drift
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
// ✅ one source, computed at render
const total = items.reduce((s, i) => s + i.price * i.qty, 0);
```
Only memoize after measuring. `useMemo` on a 20-item reduce costs more than it saves.
### 4. Colocate, Then Lift Only on Demand
Start with `useState` in the component that uses it. Lift when a second component
genuinely needs it; go global only when the consumers are far apart in the tree.
Premature globalization is what makes state hard to delete later — you can never
prove nothing reads it.
### 5. Context Re-Renders Every Consumer
```tsx
// ❌ rebuilt every render → new identity → every consumer re-renders
const value = { user, setUser };
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
// ✅ stable identity across renders
const value = useMemo(() => ({ user, setUser }), [user]);
```
Context has no selector mechanism: any change re-renders all consumers. Split
rarely-changing state (theme) from frequently-changing state (cursor position)
into separate providers, or use a store with selectors.
### 6. Select Narrowly in Stores
```ts
// ❌ re-renders on any store change
const store = useStore();
// ✅ re-renders only when this user's name changes
const name = useStore(s => s.users[id]?.name);
```
## Warning Signs
- `isLoading` / `error` booleans hand-written next to every fetch
- Filter or tab state that resets on reload and can't be linked to
- Two fields that must be updated together to stay consistent
- A global store slice read by exactly one component
- `useEffect` that copies a prop into state
- Context provider value constructed inline in JSX
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!