State Machines (XState)
Model complex UI logic with XState state machines: states, transitions, guards, actors, and replacing tangled boolean state with explicit statecharts.
State machines model complex UI flows as explicit states and transitions, eliminating impossible states and tangled boolean flags. XState is the standard JavaScript state machine library - it enforces legal state transitions and provides TypeScript-safe state modeling.
Reach for state machines when component state has more than 3 booleans or when you find yourself writing `if (isLoading && !isError && hasData)`. State machines make impossible states impossible and make the logic auditable.
Workflow
- Define a state machineExplicit states prevent impossible combinations like `isLoading: true` and `isSuccess: true` simultaneously.
- Use in React with useMachineuseMachine connects an XState machine to React rendering - the component re-renders on state transitions.
- Add guards and contextGuards add conditional transitions; context stores machine-level data across states.
- Use the Actor model for complex systemsActors are independent machines that communicate via messages - model complex multi-component systems.
- Visualize statechartsVisual statechart diagrams make the logic reviewable by non-engineers and reveal unexpected complexity.
Output Format
createMachine with explicit states + events, useMachine in React for rendering, guards for conditional transitions, assign for context updates, actors for child machines, Stately visualization.
Examples
Auth machine: `const authMachine = createMachine({ initial: "unauthenticated", states: { unauthenticated: { on: { LOGIN: "loading" } }, loading: { invoke: { src: "loginService", onDone: { target: "authenticated", actions: assign({ user: ({ event }) => event.output }) }, onError: { target: "error", actions: assign({ error: ({ event }) => event.error }) } } }, authenticated: { on: { LOGOUT: { target: "unauthenticated", actions: assign({ user: null }) } } }, error: { on: { RETRY: "loading" } } } })`Multi-step form: `const formMachine = createMachine({ initial: "step1", states: { step1: { on: { NEXT: { guard: "step1Valid", target: "step2" } } }, step2: { on: { NEXT: { guard: "step2Valid", target: "review" }, BACK: "step1" } }, review: { on: { SUBMIT: "submitting", BACK: "step2" } }, submitting: { invoke: { src: "submitForm", onDone: "success", onError: "error" } }, success: {}, error: {} } })`Type-safe events: `type MachineEvent = | { type: "FETCH"; url: string } | { type: "SUCCESS"; data: User[] } | { type: "ERROR"; error: Error }; const machine = createMachine({ types: { events: {} as MachineEvent }, ... })`Edge Cases & Gotchas
- XState v5 breaking changes: XState v5 has significant API changes from v4 (no more `Machine()`, actors replace services). Check version before following tutorials - most online examples are v4
- Serialization: XState machine definitions are JSON-serializable if you avoid inline functions. Use string references for actions and guards (`actions: "fetchData"`) and provide implementations separately
- Testing machines: use `createActor(machine).start()`, send events, and assert on snapshots. No React needed for unit testing the logic
When to Break These Rules
- For simple 2-3 state UI (loading/success/error), useState or a simple reducer is clearer than XState. State machines shine when you have 5+ states with complex transition rules.
Reference Files
XState docs: https://stately.ai/docsStately visualizer: https://stately.ai/vizXState v5 migration: https://stately.ai/docs/migrationState machines in React: https://kentcdodds.com/blog/implementing-a-simple-state-machine-library