plugins/frontend-toolkit/skills/state-management-decisions/SKILL.md
Decision framework for choosing the right state location — URL, server cache, local component, or shared/global store. Use when state-sync bugs appear, prop drilling gets deep (3+ levels), filters/tabs lose state on reload, or quarterly review. Not for form state specifically (use form-ux) or when the state is actually server data (use api-caching-optimization).
npx skillsauth add jaykim88/claude-ai-engineering state-management-decisionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Classify each piece of state by source and place it in exactly one home. Most "state management problems" are actually misclassification problems — server state in a global store, UI state duplicated across components, derivable values stored as state.
Universal — the source-classification model (derivable / URL / server / local / shared) applies to any reactive framework; only the tool names per category differ.
Classify every piece of state by source
| Source | What to do | Example |
|---|---|---|
| Derivable | Don't store — compute from input | fullName from firstName + lastName |
| URL-appropriate | Sync to URL query params | filters, tabs, page number, modal open state for shareable links |
| Server data | Server-state cache library | API responses, cached lists |
| Local component | Framework's local state primitive | form draft, hover state, single-component UI |
| Shared / global | Cross-component store with selector pattern | theme, current user object, cross-page UI state |
Detect derivable state — the highest-leverage fix (eliminated state can't desync)
useEffect.*setX), and (b) a state value that merely mirrors a prop or is recomputed each renderIdentify missing URL state
nuqs or useSearchParamsIdentify server-state-in-global-store
useStore holds users[] fetched from APICollapse related local-state atoms into one unit
form-ux skill)Move frequently-changing shared values off a broad-subscription primitive
Apply optimistic UI for instant-feedback mutations
Document decisions
useEffect syncing derived values)docs/adr/ADR-NNN-state-management-strategy.md if introducing or changing the global store choice (see decision-records skill)// state: [server|url|local|shared] because [reason] at non-obvious state declarations- Derived state eliminated: N
- URL state added: N (filter / tab / page / modal)
- Server state moved to TanStack/SWR: N
- Prop drilling fixed (max depth: was N → now ≤ 2)
| Source | Tool |
|---|---|
| URL | nuqs (Next.js) or useSearchParams |
| Server | TanStack Query / SWR / Apollo |
| Local | useState / useReducer |
| Shared | Zustand (with selectors) |
| Optimistic mutation | useOptimistic (React 19) |
useRoute().query + definePageMeta; Server → TanStack Query (Vue) / VueUse useFetch; Local → ref/reactive; Shared → Pinia (with defineStore + getters acting as selectors)$page.url.searchParams + goto(); Server → TanStack Query (Svelte) / SvelteKit +page.ts load; Local → Svelte 5 $state runes; Shared → Svelte stores or context APIRouter + ActivatedRoute.queryParams; Server → TanStack Query (Angular) or RxJS service; Local → component property / signal; Shared → service with signals or NgRx for complex casesform-ux — for form state specifically (use react-hook-form over useReducer)api-caching-optimization — when the state is actually server dataarchitecture-improvement — for cross-module state placementdevelopment
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).