agentsclimarketplace

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

Install
npx -y skills add ranbot-ai/awesome-skills --skill frontend-optimistic-mutations

Assembled 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

  1. The optimistic lifecycle is fixed. cancel → snapshot → patch → (error: roll back) → (settle: invalidate). Every optimistic mutation follows the same five beats.
  2. 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.
  3. 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.
  4. 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.
  5. 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)

SituationStrategy
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/totalPending 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.

Keep looking

Skills are one crate of 326,764. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.