> ## Documentation Index
> Fetch the complete documentation index at: https://vlyai-1c28d863.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Optimistic updates

> Show the result before the server confirms. Patterns for instant client feedback.

When a user clicks a button, the result should feel instant — even if the underlying mutation takes 200ms. **Optimistic updates** show the predicted result immediately and reconcile if the server disagrees.

## When to use

<CardGroup cols={2}>
  <Card title="✅ Toggling a state" icon="check-circle">
    Mark task done; like a post; star a comment.
  </Card>

  <Card title="✅ Adding to a list" icon="check-circle">
    Send a chat message; add a todo.
  </Card>

  <Card title="❌ Stateful side effects" icon="x-circle">
    Charging a credit card. Don't predict success — wait.
  </Card>

  <Card title="❌ Operations that often fail" icon="x-circle">
    Form submissions with strict validation. Failure recovery is more painful than the wait.
  </Card>
</CardGroup>

## Pattern with Convex

Convex's `useMutation` doesn't have optimistic updates built in. Use Zustand or component state for the optimistic layer:

```tsx theme={null}
const tasks  = useQuery(api.tasks.list, { teamId });
const toggle = useMutation(api.tasks.toggle);
const [optimistic, setOptimistic] = useState<Set<string>>(new Set());

function handleToggle(id: string) {
  // Optimistic: show toggled immediately
  setOptimistic(prev => new Set(prev).add(id));
  toggle({ id }).finally(() => {
    setOptimistic(prev => { const next = new Set(prev); next.delete(id); return next; });
  });
}

const displayed = tasks?.map(t => ({
  ...t,
  completed: optimistic.has(t._id) ? !t.completed : t.completed,
}));
```

When the mutation succeeds, `tasks` re-fetches via reactive sync; the optimistic flag is dropped, and the displayed value is now the real one.

## Reconciliation on failure

If the mutation fails, the optimistic toggle should revert:

```tsx theme={null}
toggle({ id }).catch(() => {
  toast.error("Couldn't update");
}).finally(() => {
  setOptimistic(prev => { const next = new Set(prev); next.delete(id); return next; });
});
```

The query data is the source of truth on disagreement.

## Tips

<Tip>
  **For most CRUD apps, you don't need optimistic updates.** Convex mutations resolve in 50–200ms. The UI feels fast already.
</Tip>

<Tip>
  **Optimistic updates are most valuable on slow connections.** The 200ms-on-fast-internet becomes 1000ms on a poor cell signal.
</Tip>

## Related

<CardGroup cols={3}>
  <Card title="State management" icon="layers" href="/architecture/state-management">
    Where the optimistic state lives.
  </Card>

  <Card title="Convex mutations" icon="pencil" href="/convex/mutations">
    The underlying writes.
  </Card>

  <Card title="Real-time sync" icon="zap" href="/features/data/realtime-sync">
    What reconciles after.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.