XState implements state machines and statecharts in TypeScript — application logic is defined as explicit states, events, and transitions rather than scattered isLoading, isError, isSuccess flags. createMachine({ id, initial, states, on }) defines the machine. Guards condition transitions; actions produce side effects. invoke calls async services (Promises, observables) and transitions on onDone/onError. Parallel states model concurrent behavior. useMachine(machine) runs the machine in React; useSelector(actor, selector) subscribes to specific state slices. The XState Inspector visualizes machine states and transitions in real time. The actor model with spawn creates child machines; sendTo sends events to named actors. XState v5 uses the actor model throughout — createActor(machine) creates an independent instance. Claude Code generates XState machine definitions, async service implementations, React hook integrations, and the actor model compositions for complex multi-step workflows.
CLAUDE.md for XState
## XState Stack
- Version: xstate >= 5.0, @xstate/react >= 4.0
- Machine: createMachine({ types, initial, states: { stateName: { on: { EVENT: "nextState" } } } })
- Guards: guard: ({ context, event }) => boolean — condition transitions
- Actions: assign({ count: ({ context }) => context.count + 1 }) — update context
- Async: invoke: { src: "loadOrders", onDone: { actions: assign(...) }, onError: "error" }
- React: const [state, send] = useMachine(machine) — run machine in component
- Actors: createActor(machine) — independent instance; .start() and .send()
- Inspector: import { inspect } from "@xstate/inspector" + DevTools for visualization
Order Flow Machine
// machines/order-machine.ts — multi-step order checkout machine
import { createMachine, assign, fromPromise } from "xstate"
interface OrderContext {
customerId: string
cartItems: CartItem[]
orderId: string | null
paymentIntentId: string | null
error: string | null
}
type OrderEvent =
| { type: "START"; customerId: string; cartItems: CartItem[] }
| { type: "CONFIRM_ORDER" }
| { type: "PAYMENT_METHOD_ADDED"; paymentMethodId: string }
| { type: "CONFIRM_PAYMENT" }
| { type: "RETRY" }
| { type: "CANCEL" }
export const orderMachine = createMachine(
{
id: "order",
types: {} as {
context: OrderContext
events: OrderEvent
},
initial: "idle",
context: {
customerId: "",
cartItems: [],
orderId: null,
paymentIntentId: null,
error: null,
},
states: {
idle: {
on: {
START: {
target: "creating_order",
actions: assign({
customerId: ({ event }) => event.customerId,
cartItems: ({ event }) => event.cartItems,
}),
},
},
},
creating_order: {
invoke: {
id: "createOrder",
src: "createOrderService",
input: ({ context }) => ({
customerId: context.customerId,
cartItems: context.cartItems,
}),
onDone: {
target: "awaiting_payment",
actions: assign({
orderId: ({ event }) => event.output.orderId,
paymentIntentId: ({ event }) => event.output.paymentIntentId,
error: null,
}),
},
onError: {
target: "failed",
actions: assign({
error: ({ event }) => (event.error as Error).message,
}),
},
},
},
awaiting_payment: {
on: {
CONFIRM_PAYMENT: {
target: "processing_payment",
guard: "hasPaymentIntent",
},
CANCEL: "cancelled",
},
},
processing_payment: {
invoke: {
id: "confirmPayment",
src: "confirmPaymentService",
input: ({ context }) => ({
paymentIntentId: context.paymentIntentId!,
orderId: context.orderId!,
}),
onDone: "confirmed",
onError: {
target: "payment_failed",
actions: assign({
error: ({ event }) => (event.error as Error).message,
}),
},
},
},
confirmed: {
type: "final",
},
payment_failed: {
on: {
RETRY: "awaiting_payment",
CANCEL: "cancelled",
},
},
failed: {
on: {
RETRY: "creating_order",
CANCEL: "cancelled",
},
},
cancelled: {
type: "final",
},
},
},
{
guards: {
hasPaymentIntent: ({ context }) => !!context.paymentIntentId,
},
actors: {
createOrderService: fromPromise(async ({ input }: { input: { customerId: string; cartItems: CartItem[] } }) => {
const response = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
}),
confirmPaymentService: fromPromise(async ({ input }: { input: { paymentIntentId: string; orderId: string } }) => {
const { stripe } = await import("@stripe/stripe-js").then(m => m.loadStripe(process.env.NEXT_PUBLIC_STRIPE_PK!).then(s => ({ stripe: s })))
const result = await stripe!.confirmPayment({
clientSecret: input.paymentIntentId,
confirmParams: { return_url: `${window.location.origin}/orders/${input.orderId}/confirm` },
redirect: "if_required",
})
if (result.error) throw new Error(result.error.message)
return result
}),
},
}
)
React Integration
// components/checkout/CheckoutFlow.tsx — useMachine in React
"use client"
import { useMachine } from "@xstate/react"
import { orderMachine } from "@/machines/order-machine"
import { CartSummary } from "./CartSummary"
import { PaymentForm } from "./PaymentForm"
import { OrderConfirmation } from "./OrderConfirmation"
export function CheckoutFlow({ cart }: { cart: Cart }) {
const [state, send] = useMachine(orderMachine)
// Render based on machine state
if (state.matches("idle")) {
return (
<CartSummary
cart={cart}
onCheckout={() =>
send({ type: "START", customerId: cart.customerId, cartItems: cart.items })
}
/>
)
}
if (state.matches("creating_order")) {
return <div className="py-12 text-center"><Spinner /> Creating your order...</div>
}
if (state.matches("awaiting_payment")) {
return (
<PaymentForm
orderId={state.context.orderId!}
totalCents={cart.totalCents}
onConfirmPayment={() => send({ type: "CONFIRM_PAYMENT" })}
onCancel={() => send({ type: "CANCEL" })}
/>
)
}
if (state.matches("processing_payment")) {
return <div className="py-12 text-center"><Spinner /> Processing payment...</div>
}
if (state.matches("confirmed")) {
return <OrderConfirmation orderId={state.context.orderId!} />
}
if (state.matches("payment_failed") || state.matches("failed")) {
return (
<div className="text-center py-8">
<p className="text-destructive mb-4">{state.context.error}</p>
<button onClick={() => send({ type: "RETRY" })}>Try Again</button>
<button onClick={() => send({ type: "CANCEL" })}>Cancel</button>
</div>
)
}
if (state.matches("cancelled")) {
return <p>Order cancelled. <a href="/cart">Return to cart</a></p>
}
return null
}
Actor Model with spawned machines
// machines/order-manager.ts — parent machine spawning child actors
import { createMachine, assign, spawn, sendTo, ActorRefFrom } from "xstate"
import { orderMachine } from "./order-machine"
interface ManagerContext {
activeOrders: Map<string, ActorRefFrom<typeof orderMachine>>
}
export const orderManagerMachine = createMachine({
id: "order-manager",
types: {} as { context: ManagerContext },
context: { activeOrders: new Map() },
on: {
CREATE_ORDER: {
actions: assign({
activeOrders: ({ context, event, spawn: spawnActor }) => {
const orderId = crypto.randomUUID()
const actor = spawnActor(orderMachine, { id: `order-${orderId}` })
const updated = new Map(context.activeOrders)
updated.set(orderId, actor)
return updated
},
}),
},
CANCEL_ORDER: {
actions: sendTo(
({ context, event }) => context.activeOrders.get(event.orderId)!,
{ type: "CANCEL" }
),
},
},
})
For the Zustand alternative for simpler state management without the explicit state machine model — when the states don’t form a clear finite set of valid transitions and you need more flexible state updates, see the Zustand Advanced guide for slices and middleware. For the ts-pattern pattern matching alternative that handles state dispatch with exhaustive union checks without a full state machine runtime — lighter weight for simpler state logic that doesn’t need the actor model or visualization, see the ts-pattern guide for discriminated union patterns. The Claude Skills 360 bundle includes XState skill sets covering machine definitions, async services, and React integration. Start with the free tier to try XState machine generation.