Claude Code for XState: State Machines and Statecharts — Claude Skills 360 Blog
Blog / Frontend / Claude Code for XState: State Machines and Statecharts
Frontend

Claude Code for XState: State Machines and Statecharts

Published: March 8, 2027
Read time: 8 min read
By: Claude Skills 360

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.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free