Skip to main content

Optimistic Todo List

Create, toggle, and remove todos immediately with automatic rollback.

Result

Todo interactions update the list before the mutation response. Convex confirmation replaces the optimistic view; failures restore the previous result.

Create

ts
const createTodo = useConvexMutation(api.todos.create, {
  optimisticUpdate: (store, args) => {
    updateQuery({
      query: api.todos.list,
      args: {},
      store,
      updater: (todos) => [
        {
          _id: crypto.randomUUID() as Id<'todos'>,
          _creationTime: Date.now(),
          text: args.text,
          completed: false,
        },
        ...(todos ?? []),
      ],
    })
  },
})

Toggle

ts
const toggleTodo = useConvexMutation(api.todos.toggle, {
  optimisticUpdate: (store, args) => {
    updateQuery({
      query: api.todos.list,
      args: {},
      store,
      updater: (todos) =>
        (todos ?? []).map((todo) =>
          todo._id === args.todoId ? { ...todo, completed: !todo.completed } : todo,
        ),
    })
  },
})

Remove

ts
const removeTodo = useConvexMutation(api.todos.remove, {
  optimisticUpdate: (store, args) => {
    deleteFromQuery({
      query: api.todos.list,
      args: {},
      store,
      shouldDelete: (todo) => todo._id === args.todoId,
    })
  },
})

Security boundary

The backend loads the todo and checks ownership before toggling or deleting. The optimistic predicate is presentation only.

Verify rollback

Temporarily make each mutation reject after validation and confirm:

  • a created temporary todo disappears;
  • a toggle returns to its server value;
  • a removed todo reappears;
  • the error is visible and the form remains usable.

Remove the forced failures after verification.