Skip to content

Optimistic Update

Use when: you want the UI to reflect a mutation immediately, before the server confirms it — then reconcile or roll back when the response arrives. Don’t use when: the action is destructive and hard to undo, or the server response carries data that can’t be predicted client-side.

Apply the change to local state immediately, record enough context to roll back, fire the request, and on failure restore the previous state (or re-fetch the authoritative list).

class
class TodoCubit
TodoCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> with emit / patch exposed as public mutation surface. Today it adds nothing structurally beyond StateContainer — both are inherited from the underlying StructuralContainer<S>. Kept as a real class (not a type alias) because downstream code does instance instanceof Cubit checks.

The class body is intentionally empty: a no-op emit override would still go through applyState, and patch is inherited from StructuralContainer (path-diffed, microtask-flushed). A caller that wants "skip if no real change" patch semantics can wrap patch themselves or call emit after a manual equality check.

Cubit
<
interface TodoState
TodoState
> {
constructor() {
super({
TodoState.items: Todo[]
items
: [],
TodoState.error: string | null
error
: null });
}
TodoCubit.toggleDone: (id: string) => Promise<void>
toggleDone
= async (
id: string
id
: string) => {
// 1. Snapshot for rollback.
const
const previous: Todo[]
previous
= this.
StructuralContainer<TodoState>.state: TodoState
state
.
TodoState.items: Todo[]
items
;
// 2. Apply optimistically — update only the matching item.
this.
StateContainer<TodoState, void, Record<string, never>>.patch(partial: {
items?: readonly {
id?: string | undefined;
text?: string | undefined;
done?: boolean | undefined;
}[] | undefined;
error?: string | null | undefined;
}): void

Override of StructuralContainer.patch that routes through the StateContainer concerns: disposed guard, dev-only emit-rate check, _changedWhileHydrating flag, pending-change capture (so stateChanged system events see the merged prev/next), and the registry-level stateChanged notification. We still call super.patch so path-marking semantics (the whole point of patch) are preserved.

patch
({
items?: readonly {
id?: string | undefined;
text?: string | undefined;
done?: boolean | undefined;
}[] | undefined
items
:
const previous: Todo[]
previous
.
Array<Todo>.map<Todo>(callbackfn: (value: Todo, index: number, array: Todo[]) => Todo, thisArg?: any): Todo[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
t: Todo
t
) => (
t: Todo
t
.
Todo.id: string
id
===
id: string
id
? { ...
t: Todo
t
,
Todo.done: boolean
done
: !
t: Todo
t
.
Todo.done: boolean
done
} :
t: Todo
t
)),
error?: string | null | undefined
error
: null,
});
try {
await
const api: {
markDone(id: string): Promise<void>;
}
api
.
function markDone(id: string): Promise<void>
markDone
(
id: string
id
);
// Server confirmed — nothing more to do.
} catch (
function (local var) e: unknown
e
) {
// 3. Roll back to the snapshot on failure.
// ⚠️ Do NOT ship the full state to an analytics sink before
// confirming — a rolled-back item would send false telemetry.
this.
StateContainer<TodoState, void, Record<string, never>>.patch(partial: {
items?: readonly {
id?: string | undefined;
text?: string | undefined;
done?: boolean | undefined;
}[] | undefined;
error?: string | null | undefined;
}): void

Override of StructuralContainer.patch that routes through the StateContainer concerns: disposed guard, dev-only emit-rate check, _changedWhileHydrating flag, pending-change capture (so stateChanged system events see the merged prev/next), and the registry-level stateChanged notification. We still call super.patch so path-marking semantics (the whole point of patch) are preserved.

patch
({
items?: readonly {
id?: string | undefined;
text?: string | undefined;
done?: boolean | undefined;
}[] | undefined
items
:
const previous: Todo[]
previous
,
error?: string | null | undefined
error
:
var String: StringConstructor
(value?: any) => string

Allows manipulation and formatting of text strings and determination and location of substrings within strings.

String
(
function (local var) e: unknown
e
) });
}
};
}
// Hoisted to module level so the reference is stable across renders.
// This component only calls actions and never reads state, so an empty
// selector suppresses all re-renders.
const selectNothing = () => [];
function TodoItem({
id,
text,
done,
}: {
id: string;
text: string;
done: boolean;
}) {
const [, todo] = useBloc(TodoCubit, { select: selectNothing });
return (
<li style={{ opacity: done ? 0.5 : 1 }}>
<input
type="checkbox"
checked={done}
onChange={() => todo.toggleDone(id)}
/>
{text}
</li>
);
}
  • Async — request-id guard for concurrent requests
  • Cubitpatch deep-merges; emit replaces wholesale