Rxova
Skip to content

createSharedReducer

function createSharedReducer<S, A>(
name,
reducer,
initial,
options?): SharedReducer<S, A>;

State that converges by replaying actions in one order, rather than by last-writer-wins on a value.

useSharedState’s convergence rule is last-writer-wins per key, and for a register — a theme, a selected row, a draft — that is exactly right. For an accumulating write it is exactly wrong, and the README’s own counter example was the proof: two tabs running set('n', n => n + 1) at the same moment both read 4, both write 5, and one increment is silently gone. Nothing is broken, no error is raised, and the number is simply too small.

A reducer fixes it by moving what travels. LWW ships the result of the increment, so concurrent results overwrite each other; this ships the increment, and results are computed by every client from the same ordered list. Two increments are two entries in that list.

The leader is the sequencer. A dispatch is broadcast as a propose; the leader stamps it with the next number and broadcasts a commit; every client — the leader included — applies commits strictly in that order. One list, one order, one answer, for any reducer.

Deliberately not “op-log CRDT for commutative operations”, which is cheaper and needs no leader. That design converges only if the reducer happens to be commutative, and nothing in a function’s type says whether it is. A library whose rule is that silent divergence is the worst failure mode cannot ship a primitive whose correctness depends on a property it cannot check.

A dispatch is applied locally at once and reconciled when its commit arrives, so typing is never gated on the network. If the committed order differs from the optimistic one, the value is rebuilt from committed state plus whatever is still pending — visible as a brief correction, never as a wrong result.

Leadership here inherits leadership’s own caveat: it is advisory. In the moment two tabs both believe they hold the seat, two commits can carry the same number. The second one to arrive is dropped, and the client asks for a fresh snapshot rather than guessing — so the outcome is a re-sync, not a divergence. Anything that must happen exactly once still needs a server.

Type Parameter
S
A
ParameterType
namestring
reducer(state, action) => S
initialS
optionsSharedReducerOptions

SharedReducer<S, A>