---
title: "Undo optimistic updates that fail · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/client-and-server-state/optimistic-with-rollback
updated: 2026-10-20
licence: "All rights reserved"
---

# Undo optimistic updates that fail

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Client and server state](https://agentwranglers.se/playbook/app-ui/client-and-server-state)
- Rule: When the screen changes before the server answers, keep a copy of the old data. If the request fails, put it back and say so in words. Refetch either way.
- How: Use optimistic updates only for small actions that nearly always succeed, such as a star, a rename or a checkbox. Never use them for payments, sends or deletes that others see at once. Cancel running fetches for the same key first, so they don't overwrite the change. React's `useOptimistic` does the same for one component.
- Check: A test makes the server answer 500 to a rename. The old name comes back within one second, a message says the change was not saved, and the input keeps focus.

Example:

```ts
export function useRenameProject(accountId: string) {
  return useMutation({
    mutationFn: ({ id, name }: { id: string; name: string }) => api.renameProject(accountId, id, name),
    onMutate: async ({ id, name }, context) => {
      const key = ['accounts', accountId, 'projects', id];
      await context.client.cancelQueries({ queryKey: key });
      const previous = context.client.getQueryData<Project>(key);
      context.client.setQueryData<Project>(key, old => old && { ...old, name });
      return { key, previous };
    },
    onError: (_error, _input, saved, context) => {
      if (saved) context.client.setQueryData(saved.key, saved.previous);
      announce('The new name was not saved.');
    },
    onSettled: (_data, _error, _input, saved, context) => saved && context.client.invalidateQueries({ queryKey: saved.key }),
  });
}
```

```python
@app.patch("/api/projects/<project_id>")
def rename_project(project_id):
    project = projects.rename(current_user().account_id, project_id, request.json["name"])
    return project.to_json(), 200, {"ETag": f'"{project.version}"'}
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/client-and-server-state/optimistic-with-rollback`.

- Previous: [Refresh every view a change touches](https://agentwranglers.se/playbook/app-ui/client-and-server-state/refresh-after-change)
- Next: [Keep screen state in the address](https://agentwranglers.se/playbook/app-ui/client-and-server-state/state-in-url)
- [Client and server state](https://agentwranglers.se/playbook/app-ui/client-and-server-state)
