Frontend Development5 min read

State Machines (XState)

Model complex UI logic with XState state machines: states, transitions, guards, actors, and replacing tangled boolean state with explicit statecharts.

state-machinesxstatestatechartsui-logic

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

  1. Define a state machineExplicit states prevent impossible combinations like `isLoading: true` and `isSuccess: true` simultaneously.
  2. Use in React with useMachineuseMachine connects an XState machine to React rendering - the component re-renders on state transitions.
  3. Add guards and contextGuards add conditional transitions; context stores machine-level data across states.
  4. Use the Actor model for complex systemsActors are independent machines that communicate via messages - model complex multi-component systems.
  5. 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/docs
  • Stately visualizer: https://stately.ai/viz
  • XState v5 migration: https://stately.ai/docs/migration
  • State machines in React: https://kentcdodds.com/blog/implementing-a-simple-state-machine-library

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.