A finite-state machine models a flow as named states and the transitions available from each state. A transition may move to another state or leave the current state unchanged while performing an action. This makes complex flows easier to inspect: what can happen next depends on where the flow is now. section 8.7 section 8.7.1
Start with the flow
A signup flow might require credentials, allow an optional details screen, and then require acceptance of terms. A diagram shows both backward navigation and which transitions are available at each step. Data such as a failed-login count can live in the machine’s context rather than in one particular state. section 8.7
Figure 8.18 As the visitor must have a user to proceed on a given website, they can navigate the different states of the diagram, and in the end, they will be given access to the full website. States are boxes, and transitions are arrows. Only the arrows leading out from a given state are allowed transitions from that state. Fully drawn arrows are user-initiated transitions, and dashed arrows are automatic transitions based on various criteria.
The diagram distinguishes states from transitions and shows why an arrow leaving a state defines an available next step. section 8.7
For a React list-and-detail interface, start with `list` and `single` states. Adding an item stays in the list; viewing one moves to detail; removing it returns to the list. Refine the diagram to exclude adding from detail and removing from the list. Separate events for acting on a selected item versus an item identified by ID keep the two views’ behavior explicit. section 8.7.1
An XState implementation separates the flow from its React-facing data layer:
- Define the initial state, allowed events, targets, and transition actions with `createMachine`; keep the item list and selected ID in its context. section 8.7.2
- Use `assign` actions to update context. The example keeps these actions in a separate file and performs immutable updates without requiring Immer. section 8.7.2
- Expose the interpreted machine through a React provider. Hooks select context data with `useSelector` or send events to the service, leaving presentation components behind the data-layer boundary. section 8.0 section 8.7.2
Listing 8.32 src/data/machine.js
import { createMachine } from "xstate";
import {
doThatThing,
undoThatThing,
doThisThing,
undoThisThing,
removeThing,
seeThing,
seeAllThings,
addThing,
} from "./actions";
export const machine = createMachine( #1
{
predictableActionArguments: true,
id: "100-things", #2
context: { things: [], currentThing: null }, #3
initial: "list", #3
states: {
list: { #4
on: { #4
DO_THAT: { target: "list", actions: "doThatThing" }, #4
UNDO_THAT: { target: "list", actions: "undoThatThing" }, #4
ADD: { target: "list", actions: "addThing" }, #4
SEE: { target: "single", actions: "seeThing" }, #4
}, #4
}, #4
single: { #4
on: {
DO_THIS: { target: "single", actions: "doThisThing" },
UNDO_THIS: { target: "single", actions: "undoThisThing" },
REMOVE: { target: "list", actions: "removeThing" },
SEE_ALL: { target: "list", actions: "seeAllThings" },
},
},
},
},
{
actions: {
addThing,
doThatThing,
undoThatThing,
doThisThing,
undoThisThing,
seeThing,
seeAllThings,
removeThing,
},
}
);
The machine definition makes the allowed events for each view inspectable in code. section 8.7.2
Persistence needs the whole machine state, including its context: the provider restores that state through `useInterpret` and subscribes to changes to write it to local storage. The extra stored data is a trade-off. Choose a state machine when clarifying a complex flow and its permitted transitions is worth the added modeling; React context with `useReducer`, Redux Toolkit, and Zustand remain alternatives for data management. section 8.7.2 section 8.9








