Add a mutation hook
Wire $target as a TanStack Query mutation in its feature slice. The cache is
the single source of truth for server data — the mutation invalidates or updates
it, never copies it into Zustand.
Step 1 — Locate the feature and its query keys
Find the owning feature, its request/response schema (from /add-api-contract),
the existing query-key factory, and the project API client (shared/lib/api).
Reuse the key factory; do not invent parallel keys.
Step 2 — Define the mutation
Apply references/mutation-patterns.md.
- Type
mutationFninput from the request schema (z.infer); parse the response before returning it. - Call the API client; do not
fetchinline.
Step 3 — Keep the cache correct
Choose one:
- Invalidate (default):
onSuccess→queryClient.invalidateQuerieson the affected keys. Simple and correct. - Optimistic (only when UX needs instant feedback):
onMutatesnapshots and writes the cache,onErrorrolls back to the snapshot,onSettledinvalidates. Do not optimistically update without a rollback path.
Step 4 — Surface state, don't duplicate it
Return isPending/error for callers to drive disabled states and messages.
Never mirror the mutated entity into a Zustand store — read it back from the
query cache.
Step 5 — Verify
source "${CLAUDE_PROJECT_DIR}/.claude/lib/detect-toolchain.sh"
run_typecheck
run_tests <feature-hook-test>
Test with a fresh QueryClient and retry: false; assert the cache is
invalidated/updated and that an error rolls back any optimistic write.
Checklist
-
mutationFninput typed from the request schema; response parsed. - Submission goes through the API client, not inline
fetch. - Affected query keys are invalidated, or optimistically updated with rollback.
- No server data mirrored into Zustand.
- Hook test uses a fresh
QueryClientwithretry: false;run_typecheckpasses.