Skip to content

createState in Depth

The Quick Example showed that createState returns a 3-element tuple. This page covers the third element in detail, along with key design differences from React’s useState.

const [
state,
setState,
{ updateState, resetState, getSnapshot, initialState },
] = createState(0);
FunctionTypeDescription
setState(v: S) => SSet the state to a new value
updateState(updateFn: (prev: S) => S) => SUpdate the state using a function of previous value
resetState() => SReset the state to its initial value
getSnapshot() => SRead the current value synchronously
initialStateSThe initial value passed to createState

Unlike React’s useState setter, which schedules asynchronous re-renders, setState, updateState, and resetState in SynState execute synchronously and return the updated state value. You can also call getSnapshot() immediately after a state update to retrieve the latest value.

const [
state,
setState,
{ updateState, resetState, getSnapshot, initialState },
] = createState(0);
// setState returns the new state value synchronously
const newValue = setState(42);
console.log(newValue); // 42
// updateState also returns the new state value
const updated = updateState((prev) => prev + 1);
console.log(updated); // 43
// getSnapshot() reflects the latest state immediately
console.log(getSnapshot()); // 43
// resetState returns the initial state value
const reset = resetState();
console.log(reset); // 0

Design Choice: Separate setState and updateState

Section titled “Design Choice: Separate setState and updateState”

In React’s useState, the setter function accepts both a direct value and an updater function (overloaded):

// React useState — single setter handles both forms
setCount(5); // direct value
setCount((prev) => prev + 1); // updater function

SynState intentionally separates these into two distinct functions — setState for setting a value directly and updateState for updating based on the previous state:

// SynState — explicit, separate functions
setState(5); // direct value
updateState((prev) => prev + 1); // updater function

This avoids the ambiguity of overloaded signatures and makes the intent of each call explicit. There is no runtime guessing about whether the argument is a value or a function.

For state that transitions through well-defined actions, createReducer provides a familiar Redux-like pattern. Instead of setState / updateState, you dispatch typed actions:

type Action = Readonly<{ type: 'increment' } | { type: 'decrement' }>;
const [state, dispatch] = createReducer((s: number, action: Action) => {
switch (action.type) {
case 'increment':
return s + 1;
case 'decrement':
return s - 1;
}
}, 0);
dispatch({ type: 'increment' }); // state is now 1

Like createState, dispatch executes synchronously and returns the new state. The returned state is an InitializedObservable that you can subscribe to or derive from with operators.

See the API reference for createReducer for a full example.

For boolean flags (modals, drawers, dark mode, etc.), createBooleanState provides convenient named methods so you don’t need to write () => setState(true) wrappers:

const [isOpen$, { setTrue: open, setFalse: close, toggle }] =
createBooleanState(false);
open(); // isOpen$ emits true
close(); // isOpen$ emits false
toggle(); // isOpen$ emits true

The returned methods (setTrue, setFalse, toggle) have stable references and can be passed directly as event handler callbacks.

See the API reference for createBooleanState for a full example with React integration.