--- name: xstate-react description: This skill should be used when the user asks to "create a state machine", "add xState", "use xState with React", "implement actor-based state", "manage complex state with state machines", "use xState with Effect", "integrate Effect-ts with xState", mentions xState hooks (useMachine, useActor, useSelector), or discusses finite state machines in React applications. --- # xState React ## Your Role You are an expert in xState v5 actor-based state management with React and TypeScript. You understand state machines, statecharts, the actor model, and React integration patterns for managing complex application logic. ## Overview xState is an actor-based state management and orchestration solution for JavaScript and TypeScript applications. It uses event-driven programming, state machines, statecharts, and the actor model to handle complex logic in predictable, robust, and visual ways. **When to use xState:** - Complex UI flows (multi-step forms, wizards, checkout processes) - State with many transitions and edge cases - Logic that needs to be visualized and validated - Processes with async operations and error handling - State that can be in multiple "modes" (loading, error, success, idle) **When to use simpler state instead (useState, Zustand):** - Simple UI toggles and counters - Form state confined to a single component - State without complex transition logic - CRUD operations with straightforward loading states ## Quick Start Create a state machine and use it in a React component: ```typescript "use client"; import { createMachine } from "xstate"; import { useMachine } from "@xstate/react"; const toggleMachine = createMachine({ id: "toggle", initial: "inactive", states: { inactive: { on: { TOGGLE: "active" } }, active: { on: { TOGGLE: "inactive" } } } }); function Toggle() { const [state, send] = useMachine(toggleMachine); return ( ); } ``` ## React Hooks API ### `useMachine` Create and run a machine within a component's lifecycle: ```typescript import { useMachine } from "@xstate/react"; import { someMachine } from "./machines/someMachine"; function Component() { const [state, send, actorRef] = useMachine(someMachine, { input: { userId: "123" } // Optional input }); return (

Current state: {JSON.stringify(state.value)}

Context: {JSON.stringify(state.context)}

); } ``` ### `useActor` Subscribe to an existing actor (created outside the component): ```typescript import { createActor } from "xstate"; import { useActor } from "@xstate/react"; import { todoMachine } from "./machines/todoMachine"; // Create actor outside component (e.g., in a module or context) const todoActor = createActor(todoMachine); todoActor.start(); function TodoApp() { const [state, send] = useActor(todoActor); return ( ); } ``` ### `useSelector` Optimize re-renders by selecting specific state: ```typescript import { useSelector } from "@xstate/react"; function TodoCount({ actorRef }) { // Only re-renders when todos.length changes const count = useSelector(actorRef, (state) => state.context.todos.length); return {count} todos; } function IsLoading({ actorRef }) { // Only re-renders when loading state changes const isLoading = useSelector(actorRef, (state) => state.matches("loading")); return isLoading ? : null; } ``` ## TypeScript Patterns ### Typing Machines with `types` Define context and events using the `types` property: ```typescript import { createMachine, assign } from "xstate"; type FormContext = { name: string; email: string; errors: string[]; }; type FormEvent = | { type: "UPDATE_NAME"; value: string } | { type: "UPDATE_EMAIL"; value: string } | { type: "SUBMIT" } | { type: "RESET" }; const formMachine = createMachine({ types: {} as { context: FormContext; events: FormEvent; }, id: "form", initial: "editing", context: { name: "", email: "", errors: [] }, states: { editing: { on: { UPDATE_NAME: { actions: assign({ name: ({ event }) => event.value }) }, UPDATE_EMAIL: { actions: assign({ email: ({ event }) => event.value }) }, SUBMIT: "submitting" } }, submitting: { // ... } } }); ``` ### Using `setup()` for Reusable Definitions Define actions, guards, actors, and delays in a type-safe way: ```typescript import { setup, assign } from "xstate"; type AuthContext = { userId: string | null; retries: number; }; type AuthEvent = | { type: "LOGIN"; username: string; password: string } | { type: "LOGOUT" } | { type: "SUCCESS"; userId: string } | { type: "FAILURE" }; const authMachine = setup({ types: {} as { context: AuthContext; events: AuthEvent; }, actions: { setUser: assign({ userId: ({ event }) => (event as { userId: string }).userId }), clearUser: assign({ userId: null, retries: 0 }), incrementRetries: assign({ retries: ({ context }) => context.retries + 1 }) }, guards: { hasReachedMaxRetries: ({ context }) => context.retries >= 3, isAuthenticated: ({ context }) => context.userId !== null } }).createMachine({ id: "auth", initial: "loggedOut", context: { userId: null, retries: 0 }, states: { loggedOut: { on: { LOGIN: "authenticating" } }, authenticating: { on: { SUCCESS: { target: "loggedIn", actions: "setUser" }, FAILURE: [ { guard: "hasReachedMaxRetries", target: "loggedOut", actions: "clearUser" }, { actions: "incrementRetries" } ] } }, loggedIn: { on: { LOGOUT: { target: "loggedOut", actions: "clearUser" } } } } }); ``` ## Core Concepts ### States and Transitions States represent the possible modes of your system. Transitions define how events move between states: ```typescript const machine = createMachine({ initial: "idle", states: { idle: { on: { FETCH: "loading" } }, loading: { on: { SUCCESS: "success", ERROR: "error" } }, success: { type: "final" }, error: { on: { RETRY: "loading" } } } }); ``` ### Context Context holds extended state data: ```typescript const machine = createMachine({ context: { count: 0, user: null }, // ... }); // Access in component const count = state.context.count; ``` ### Actions Actions are fire-and-forget side effects: ```typescript import { assign } from "xstate"; const machine = createMachine({ // ... states: { active: { entry: assign({ count: ({ context }) => context.count + 1 }), exit: () => console.log("Leaving active state") } } }); ``` ## Best Practices ### DO - Use `setup()` for type-safe action and guard definitions - Use `useSelector` for optimized state selection - Define explicit types for context and events - Use `state.matches()` for hierarchical state checking - Keep machines pure and side-effect-free (use actions for effects) ### DON'T - Don't store the entire snapshot object in React state - Don't mutate context directly (use `assign`) - Don't use xState for simple toggle/counter state - Don't forget to handle all possible states in your UI ## Client Component Requirement xState hooks must be used in Client Components. Add the `"use client"` directive: ```typescript "use client"; import { useMachine } from "@xstate/react"; ``` ## Additional Documentation For comprehensive xState documentation including advanced patterns, use the Context7 MCP: ``` Use Context7 MCP with library ID "/statelyai/xstate" to fetch: - Detailed invoke/spawn patterns - Parallel and history states - Actor communication - Testing strategies ``` **See `./references/PATTERNS.md`** for common patterns including async operations, guards, parallel states, and persistence. **See `./references/EFFECT_TS_INTEGRATION.md`** for integrating Effect-ts with xState for composable, type-safe side effects. ## Quick Reference | Task | Pattern | | -------------------- | ---------------------------------------------------- | | **Create machine** | `createMachine({ id, initial, states, context })` | | **Use in component** | `const [state, send] = useMachine(machine)` | | **Check state** | `state.matches("loading")` or `state.value` | | **Send event** | `send({ type: "EVENT_NAME", ...payload })` | | **Read context** | `state.context.someValue` | | **Update context** | `assign({ key: ({ context, event }) => newValue })` | | **Conditional** | `guard: "guardName"` or `guard: ({ context }) => …` | | **Async operation** | `invoke: { src: fromPromise(...), onDone, onError }` | | **Optimized select** | `useSelector(actorRef, (state) => state.context.x)` |