Fullstack CourseLearn by building
Back to week 3

Topic

Mutate data safely

Definition

A mutation is a state-changing request whose UI must represent pending, success, and failure states before synchronized data is refreshed.

In simpler words

Send the write, prevent duplicate clicks while waiting, show failures, and refresh the data that changed.

Interactive product writes use Nest endpoints and TanStack Query mutations in this monorepo.

After this you can

  • Model pending, success, and error states
  • Invalidate affected queries after a write
  • Distinguish Next-owned actions from Nest-owned product writes

Make the mutation visible

A mutation has a lifecycle, not merely an await expression. Disable or otherwise protect the submit action while pending, retain the draft on failure, and give the user an actionable error message.

After success, invalidate the exact Query resource whose representation is stale. A draft form is local client state; RTK is reserved here for client drafts, not as a duplicate cache of Nest ticket lists.

Create and refresh tickets

const createTicket = useMutation({
  mutationFn: ticketsApi.create,
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['tickets'] }),
});

<button disabled={createTicket.isPending}>Create ticket</button>

The mutation owns its pending state and marks the list stale after success.

Mistake: navigating before the write succeeds

// Wrong — leaves the page even if Nest rejects the body
onClick={() => { createTicket.mutate(draft); router.push('/tickets'); }}

// Right — navigate in onSuccess after accepted write
onSuccess: (ticket) => router.push(`/tickets/${ticket.id}`)

A route change is not a successful mutation.

Use Server Actions by ownership

Definition

Server Actions can suit Next-owned form workflows, but they are not a reason to move the product API into Next.

In simpler words

Choose the mutation path that owns validation, authorization, and persistence.

Keep optimistic updates only when the rollback behavior is clear and the UI benefits are measurable.

Do not use a browser-side success message as proof that a write was authorized.

Live playground

Mutating data playground

Trigger a mutation and identify the pending, error, and success UI.

Idle

Keep in mind

  • Disable submit controls while the mutation is pending.
  • Invalidate the precise affected Query key after success.
  • Keep drafts local or in RTK; keep remote ticket records in Query.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥80% (enforced by the API) to save progress.

Checking your session…

15 questions · concept 5 · syntax 3 · practical 4 · logic 3

1. Server Actions idea?
Concept
2. Forms with actions?
Syntax
3. Revalidate after mutation?
Practical
4. When still call Nest from the client?
Logic
5. Optimistic UI?
Concept
6. Validation location?
Practical
7. Idempotent deletes?
Syntax
8. Cookies on mutations to Nest?
Logic
9. Why not mutate DB from a Client Component directly?
Concept
10. Which mutation practice is sound?
Practical
11. What is this?
Conceptintermediate
'use server';
export async function createTicket(formData) { /* ... */ }
12. What is missing after this mutation?
Syntaxadvanced
export async function createPost(data) {
  await db.post.create(data);
  // list still shows stale data
}
13. Why include credentials when mutating the Nest API?
Practicalintermediate
await api.post('/tickets', body, { withCredentials: true });
14. Why is client-only validation insufficient?
Logicadvanced
// only validate in the browser before submit
15. What is wrong with this delete?
Conceptadvanced
'use client';
async function remove(id) {
  await db.tickets.delete(id); // in the browser
}

Checking your session…