export interface Todo {
id: string;
title: string;
done: boolean;
pending: boolean;
}
export type TodoAction =
| { type: 'TOGGLE_OPTIMISTIC'; id: string }
| { type: 'TOGGLE_CONFIRMED'; id: string }
| { type: 'TOGGLE_ROLLBACK'; id: string };
export function todosReducer(state: Todo[], action: TodoAction): Todo[] {
switch (action.type) {
case 'TOGGLE_OPTIMISTIC':
return state.map((t) =>
t.id === action.id ? { ...t, done: !t.done, pending: true } : t
);
case 'TOGGLE_CONFIRMED':
return state.map((t) =>
t.id === action.id ? { ...t, pending: false } : t
);
case 'TOGGLE_ROLLBACK':
return state.map((t) =>
t.id === action.id ? { ...t, done: !t.done, pending: false } : t
);
default:
return state;
}
}
import { useReducer, useCallback, useState } from 'react';
import { todosReducer, Todo } from './todosReducer';
import { api } from './api';
export function useOptimisticTodos(initial: Todo[]) {
const [todos, dispatch] = useReducer(todosReducer, initial);
const [error, setError] = useState<string | null>(null);
const toggle = useCallback(
async (id: string) => {
const current = todos.find((t) => t.id === id);
if (!current || current.pending) return;
const nextDone = !current.done;
dispatch({ type: 'TOGGLE_OPTIMISTIC', id });
setError(null);
try {
await api.updateTodo(id, { done: nextDone });
dispatch({ type: 'TOGGLE_CONFIRMED', id });
} catch (err) {
dispatch({ type: 'TOGGLE_ROLLBACK', id });
setError(err instanceof Error ? err.message : 'Failed to update todo');
}
},
[todos]
);
return { todos, toggle, error };
}
import React from 'react';
import { useOptimisticTodos } from './useOptimisticTodos';
import { Todo } from './todosReducer';
export function TodoList({ initial }: { initial: Todo[] }) {
const { todos, toggle, error } = useOptimisticTodos(initial);
return (
<div className="todo-list">
{error && (
<div role="alert" className="todo-error">
{error}
</div>
)}
<ul>
{todos.map((todo) => (
<li key={todo.id} className={todo.pending ? 'is-pending' : ''}>
<label>
<input
type="checkbox"
checked={todo.done}
disabled={todo.pending}
onChange={() => toggle(todo.id)}
/>
<span className={todo.done ? 'done' : ''}>{todo.title}</span>
</label>
{todo.pending && <span className="spinner" aria-hidden />}
</li>
))}
</ul>
</div>
);
}
This snippet shows the optimistic-UI pattern applied to a to-do completion toggle: the interface updates the checkbox immediately, then reconciles with the server once the network request settles. The core idea is that most mutations succeed, so making the user wait for a round trip before reflecting the change is wasteful. Instead the UI assumes success, and only rolls back the affected item if the request actually fails.
In todosReducer.ts, the reducer models each todo as having its own done flag plus a transient pending flag. The TOGGLE_OPTIMISTIC action flips done right away and marks the item pending so the row can render a busy state and disable further clicks. TOGGLE_CONFIRMED simply clears pending, while TOGGLE_ROLLBACK restores the previous done value and clears pending. Keeping rollback logic inside the reducer means the previous value is derived from state itself rather than captured in a closure, which avoids stale-value bugs when several toggles race.
In useOptimisticTodos.ts, the custom hook wires the reducer to an async API call. toggle dispatches the optimistic action, computes the intended done value, and calls api.updateTodo. On success it dispatches TOGGLE_CONFIRMED; on failure it dispatches TOGGLE_ROLLBACK and surfaces the error message so the component can inform the user. Because each toggle is keyed by id, concurrent toggles on different rows do not interfere. The hook is careful to always resolve the pending state in both branches, so a failed request can never leave a row stuck in a spinner.
In TodoList.tsx, the component consumes the hook and renders each row. The checkbox onChange calls toggle(todo.id), and the input is disabled while todo.pending is true to prevent double submissions. A transient error banner appears when a rollback occurs.
The main trade-off is consistency versus responsiveness: the UI briefly shows a state the server has not confirmed, so rollback must be visible and honest. This pattern fits low-risk, high-frequency mutations like toggles, likes, and reorders, and is less appropriate for operations with side effects the user must trust immediately.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
import SwiftUI
struct ContentView: View {
@State private var username = ""
@State private var isLoggedIn = false
@StateObject private var viewModel = LoginViewModel()
SwiftUI declarative UI with state management
Share this code
Here's the card — post it anywhere.