Frontend optimistic mutations
Skill ranbot-ai/awesome-skills/skills/frontend-optimistic-mutations
A portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer. Codifies the optimistic-update lifecycle (cancel in-flight queries → snapshotFrom its SKILL.md
npx -y skills add ranbot-ai/awesome-skills --skill frontend-optimistic-mutationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.4 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it
Frontend Optimistic Mutations (the write path)
When to Use
Use this skill when you need a portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer. Codifies the optimistic-update lifecycle (cancel in-flight queries → snapshot every affected cache → patch instantly → roll back verbatim on error → invalidate on...
Portable skill — readable by Claude Code, OpenCode, Codex, Cursor, Windsurf, and others. This skill describes the discipline of the write path — optimistic updates, rollback, idempotency, cache coherence — not a UI library or a styling system. It builds directly on the frontend-data-contracts skill (writes go through the typed client) and the frontend-architecture skill (mutations live in
modules/{feature}/hooks/, keyed by a factory).
The goal: a write feels instant (the UI reflects it before the server confirms), is safe (a failure restores the exact prior state, and a retry never double-charges), and leaves the cache coherent (the detail view and every list page agree). All three at once — that's the craft.
0. The five core ideas
- The optimistic lifecycle is fixed. cancel → snapshot → patch → (error: roll back) → (settle: invalidate). Every optimistic mutation follows the same five beats.
- Roll back verbatim. On failure, restore the exact snapshot taken before the patch — not a "best guess" re-derivation. Keep the snapshot in mutation context.
- Idempotency is generated once, not per attempt. The key is created at form init (or first intent), so a network retry replays the original server response instead of performing the action twice.
- Caches move in lock-step. A status change patches the detail cache and every list page that contains the entity, so badges never disagree across surfaces.
- Server state never enters the client store. Optimistic state lives in the query cache, not Zustand/Redux. The cache is the single source of truth for server data (per frontend-architecture §4).
1. When to be optimistic (and when not)
| Situation | Strategy |
|---|---|
| High-confidence, low-conflict write (toggle status, like, mark-paid, reorder) | Optimistic — patch immediately, roll back on error. |
| Create that returns a server-generated id/number/total | Pending state, then setQueryData from the server response. A temporary optimistic row is optional; reconcile on success. |
| Destructive or hard-to-reverse write (delete with cascade, send money) | Confirm first, then optimistic or pending — never silent-optimistic. |
| Write whose result the user can't see yet (background job) | Pending + toast, invalidate when done. No optimistic patch. |
Optimism is a UX tool for writes you're confident will succeed. If failure is common or expensive to undo, prefer a pending state.
2. The optimistic lifecycle (TanStack Query)
The canonical shape. Each beat has a job; skipping one breaks correctness.
// modules/invoice/hooks/useInvoiceMutations.ts
interface MarkPaidContext {
previousInvoice: Invoice | undefined; // detail snapshot
previousLists: Array<[readonly unknown[], InvoiceListResponse]>; // every list page snapshot
}
export function useMarkInvoicePaid() {
const queryClient = useQueryClient();
const notifyError = useApiErrorToast();
return useMutation<Invoice, ApiError, { id: InvoiceId }, MarkPaidContext>({
mutationFn: ({ id }) => apiClient.post<Invoice>(INVOICE_API.markPaid(id)),
// 1 + 2 + 3: cancel in-flight reads, snapshot, patch
onMutate: async ({ id }) => {
await queryClient.cancelQueries({ queryKey: invoiceKeys.all }); // (1) no late refetch clobber
const detailKey = invoiceKeys.detail(id);
const previousInvoice = queryClient.getQueryData<Invoice>(detailKey); // (2) snapshot detail
if (previousInvoice) {
queryClient.setQueryData<Invoice>(detailKey, {
// (3) patch detail
...previousInvoice,
status: InvoiceStatus.Paid,
});
}
const previousLists: MarkPaidContext["previousLists"] = [];
for (const [key, list] of queryClient.getQueriesData<InvoiceListResponse>(
{
queryKey: invoiceKeys.lists(),
},
)) {
if (!list) continue;
previousLists.push([key, li
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.