Rxova
Skip to content

useSharedReducer

function useSharedReducer<S, A>(
reducer,
initial,
options?): [S, (action) => void];

Like useReducer, but every tab, window, and worker on this origin applies the same actions in the same order.

const [count, dispatch] = useSharedReducer((n, action) => n + action.by, 0);
<button onClick={() => dispatch({ by: 1 })}>{count}</button>;

Reach for this instead of useSharedState whenever a write is relative to what is already there — a counter, a total, a list you append to. Shared state converges last-writer-wins on the value, so two tabs incrementing at once both write the same result and one increment vanishes. A reducer sends the action rather than the result, and two increments are two actions.

For a plain register — a theme, a selection, a draft — useSharedState is still the right tool and the cheaper one.

Actions must survive the wire. They are structured-cloned to every peer, so no functions, no class instances, no DOM nodes.

The reducer must be pure, and it must be the same reducer everywhere. Every client folds the same actions itself; a client whose fold differs gets a different answer, and nothing can detect that. The first caller’s function is the one used for the life of the page — a re-render passes a new identity every time, and swapping the fold under a history already applied is exactly the divergence this exists to prevent.

Ordering needs a leader, which this shares with useLeader on the same bus rather than electing a second one. Before a seat is filled — the first moments of the first tab — dispatches are held locally and ordered once it is. They are never lost, only pending.

Type Parameter
S
A
ParameterType
reducer(state, action) => S
initialS
options?UseSharedReducerOptions

[S, (action) => void]