A primitive for creating a reactive state machine. For expressing possible exclusive states and transitions, and bounding reactive computations to the lifecycle of those states.
createMachine is a simple primitive for creating a reactive state machine. It takes a configuration object with the following properties:
initial - The initial state of the machine.
states - Implementation of the states of the machine. Each state implements a callback called when the machine enters that state with parameters received from the transition. Value returned from the callback will be available as the value of the state.
createMachine requires passing a type parameter defining the states. It expects an object with keys being the names of the states and values being objects with the following properties:
input - Value to be passed to the state callback when the machine enters that state.
value - Value returned from the state callback.
to - Union of state names that can be transitioned to from this state. If not provided, any state can be transitioned to from this state. never will create a terminal state.
Value returned from createMachine is a signal with the following properties:
type - Current state of the machine.
value - Value returned from the state callback.
to - Function for transitioning to another state. It takes a state name and optional input for the state callback.
constv=state();
v.type; // "idle"
v.value; // "foo"
if (v.type ==="idle") {
v.to.loading(1000);
// state is now "loading" after the next reactive flush
}
The state properties are also implemented as getters on the function itself:
state.type; // "idle"
state.value; // "foo"
if (state.type ==="idle") {
state.to.loading(1000);
}
Note: Transitions via state.to.*() are batched and applied asynchronously (microtask). In reactive contexts such as JSX or effects, the updated state is reflected automatically. In tests or imperative code, call flush() from solid-js after a transition to read the updated state synchronously.
createMachine is implemented using createMemo, which reruns when the state is changed. This means that any reactive computations can be used inside the state callbacks and they will be disposed when the state changes. (owner context will be available in the callbacks)
State callbacks that immediately call next.*() (inline transitions to another state) are resolved synchronously within the same reactive computation — no flush is needed to see the final settled state.
conststate=createMachine({
initial: "counter",
states: {
counter() {
const [count, setCount] =createSignal(0);
constinterval=setInterval(() =>setCount(c=> c +1), 1000);
createMachine can be used for handling events in a declarative way. Although it doesn't implement anything special for handling events, any function can be returned from the state callback and it will be called when the event is triggered.
typeEvents= {
NEXT: () =>void;
// make events optional to not have to
// implement them in every state
RESET?: () =>void;
};
conststate=createMachine<{
red: {
value:Events;
// you can limit the states that can be transitioned to
to:"yellow";
};
yellow: {
value:Events;
to:"green"|"red";
};
green: {
value:Events;
to:"red";
};
}>({
initial: "red",
states: {
red(_, next) {
return {
NEXT: () => next.yellow(),
};
},
yellow(_, next) {
return {
NEXT: () => next.green(),
RESET: () => next.red(),
};
},
green(_, next) {
return {
NEXT: () => next.red(),
RESET: () => next.red(),
};
},
},
});
state.value.NEXT(); // transition to the next state
state.value.RESET?.(); // reset to the initial state
To avoid recreating the state machine callbacks each time, the state implementation object can be hoisted outside of the createMachine call.
Then to define a way for the machine to communicate with the outside world, declate a shared type for the state inputs (kinda like component props), and use it when initializing the machine.