Skip to content

createReducer

Creates a reducer-based state management container following the Redux pattern. Actions are dispatched to update the state according to the reducer function.

const [state, dispatch] = createReducer(
(s, action: Readonly<{ type: 'increment' } | { type: 'decrement' }>) => {
switch (action.type) {
case 'increment':
return s + 1;
case 'decrement':
return s - 1;
}
},
0,
);
const stateHistory: number[] = [];
state.subscribe((value: number) => {
stateHistory.push(value);
});
assert.deepStrictEqual(stateHistory, [0]);
dispatch({ type: 'increment' }); // logs: 1
assert.deepStrictEqual(stateHistory, [0, 1]);
dispatch({ type: 'increment' });
dispatch({ type: 'decrement' });
assert.deepStrictEqual(stateHistory, [0, 1, 2, 1]);

The third argument takes an equals option.

const [count, dispatch] = createReducer(
(s, action: Readonly<{ type: 'add'; amount: number }>) => s + action.amount,
0,
{ equals: () => false },
);
const countHistory: number[] = [];
count.subscribe((value: number) => {
countHistory.push(value);
});
dispatch({ type: 'add', amount: 0 }); // 0 again, passed on all the same
dispatch({ type: 'add', amount: 2 });
assert.deepStrictEqual(countHistory, [0, 0, 2]);

equals is called with the current and the next state. When it returns true, the update is not passed on: no subscriber, and nothing derived from the state, is told of it, and the current state is kept along with its identity (dispatch returns the current state rather than the next). Decide this where the state is held rather than downstream with skipIfNoChange: an update passed on here reaches whatever reads the state directly before any operator can thin it out.

equals defaults to Object.is, so writing the value already held passes nothing on. Pass equals: () => false to pass on every update, equal or not.

View source on GitHub