xstate-v5

Implement, refactor, and review XState v5 state machines (TypeScript and React) using a strict setup().createMachine() ruleset, params-first typing, canonical actor/spawn patterns, and createActorContext() patterns from @xstate/react. Includes guidance for planning/designing statecharts before implementation.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "xstate-v5" with this command: npx skills add programbo/xstate-skill/programbo-xstate-skill-xstate-v5

xstate-v5 (Strict) Skill

Build, refactor, and review XState v5 state machines (TypeScript and React) using a strict, strongly-typed ruleset.

Source of truth: references/xstate-v5-rules.md (this repo, resolved from the skill root). If anything in this skill conflicts with the rules file, follow the rules file.

Inlined Non-Negotiable Contract (Agent Must Follow)

These requirements are intentionally duplicated here so an agent can comply without first loading external references:

  • Design first, code second:
    • Produce planning artifacts before implementation (state inventory, event catalog, transition table, async/actor map, acceptance-test scenarios).
    • Include a boundary/decomposition decision record (what is split vs orchestrated, and why).
  • No god-machine architecture:
    • Unrelated domains (auth, toasts, navigation, workflows, transport/retry policy) must not be fully modeled in one machine.
    • A single machine is acceptable only as a thin orchestration root or tightly coupled app-shell parallel regions.
  • No state-mirroring context flags:
    • Do not duplicate mode in context booleans (isLoading, isAuthenticated, etc.) when state.value already represents the mode.
    • If a temporary migration flag exists, document rationale and removal plan.
  • XState v5 strictness:
    • Use setup({...}).createMachine({...}).
    • Implement actions/guards/actors in setup(...).
    • Send event objects only.
    • Avoid forbidden v4 patterns (interpret, Machine, cond, send, pure, choose, etc.).
  • Enforce explicit async and actor boundaries:
    • Request/response: invoke with onDone and onError.
    • Long-lived collaborators: spawnChild/stopChild and explicit routing via sendTo.

Fail-closed rule:

  • If any detail is ambiguous, load references/xstate-v5-rules.md before writing or reviewing machine code.
  • If that file is missing/unreadable, stop and report the issue instead of guessing.

When To Use

Use this skill when the task involves:

  • XState v5 machines/actors (xstate, @xstate/react)
  • Refactoring XState code to be more type-safe and idiomatic v5
  • Migrating XState v4 patterns (Machine/interpret/cond/send/pure/choose/etc.) to v5 equivalents
  • Planning/designing statecharts prior to implementing them as XState v5 machines
  • Designing actor boundaries (invoke vs spawnChild) and React integration via createActorContext

Hard Requirements (Enforced)

  • Prefer setup({...}).createMachine({...}) for all machines.
  • Implement all actions, guards, and actors in the setup({...}) object.
  • Prefer passing event-derived data via typed params to actions/guards.
  • Never send string events. Always send event objects: actor.send({ type: '...' }).
  • Forbid XState v4 legacy APIs and patterns listed in references/xstate-v5-rules.md.
  • Do not model unrelated domains (auth, notifications/toasts, navigation, data lifecycle, etc.) inside one machine, except a thin orchestration machine.
  • Do not mirror state in context booleans (isLoading, isAuthenticated, etc.) when state.value already represents that mode.

Rules Table Of Contents

Quick Start

Minimal typed machine skeleton:

import { setup, assign } from "xstate"

type Ctx = { count: number }

type Ev = { type: "inc" } | { type: "add"; amount: number } | { type: "reset" }

type Input = { initialCount?: number }

export const counterMachine = setup({
  types: {
    context: {} as Ctx,
    events: {} as Ev,
    input: {} as Input,
  },
  actions: {
    inc: assign({ count: ({ context }) => context.count + 1 }),
    add: assign({
      count: ({ context }, params: { amount: number }) => context.count + params.amount,
    }),
    reset: assign({ count: ({ input }) => input.initialCount ?? 0 }),
  },
}).createMachine({
  id: "counter",
  context: ({ input }) => ({ count: input.initialCount ?? 0 }),
  on: {
    inc: { actions: "inc" },
    add: {
      actions: {
        type: "add",
        params: ({ event }) => ({ amount: event.amount }),
      },
    },
    reset: { actions: "reset" },
  },
})

React shared state skeleton:

import { createActorContext, shallowEqual } from "@xstate/react"
import { type SnapshotFrom } from "xstate"
import { counterMachine } from "./counterMachine"

const CounterCtx = createActorContext(counterMachine)

export const CounterProvider = CounterCtx.Provider
export const useCounterSelector = CounterCtx.useSelector
export const useCounterActorRef = CounterCtx.useActorRef

const selectCount = (s: SnapshotFrom<typeof counterMachine>) => s.context.count
export const useCount = () => useCounterSelector(selectCount)

const selectCtx = (s: SnapshotFrom<typeof counterMachine>) => s.context
export const useCounterContext = () => useCounterSelector(selectCtx, shallowEqual)

Workflow (What To Do Each Time)

  1. Read references/xstate-v5-rules.md first.
  2. Identify the target surface area:
    • Plain machine/actor (non-React)
    • React integration (createActorContext) or local useActor
    • Migration from v4 patterns
  3. Design the statechart first:
    • See references/xstate-v5-rules.md#0-statechart-design-before-you-code.
    • Write down: state inventory, event catalog (payload + source), transition table, async/actor boundaries, and acceptance-test scenarios.
    • Record a boundary/decomposition decision before type design:
      • Which concerns belong in separate actors/machines (based on lifecycle, owner, and failure mode).
      • Which concerns are intentionally orchestrated at top-level (and why).
      • Any exception note if keeping logic in one machine.
    • Default: do this unless the user explicitly asks to skip planning.
  4. Design types first:
    • types.context, types.events, and types.input (and types.output if applicable)
    • Use zod schemas only if the codebase already uses Zod or the user asks for it.
  5. Put implementations in setup({ actions, guards, actors }):
    • Actions/guards take typed params when they need event-derived data.
    • Only read event-specific fields inside implementations when necessary; use assertEvent then.
  6. Choose async boundaries:
    • Prefer invoke with typed input for request/response flows.
    • Use spawnChild/stopChild for long-lived child actors.
  7. Use v5 runtime APIs:
    • createActor(machine) (not interpret)
    • raise / sendTo (not send action)
    • enqueueActions (not pure/choose)
  8. Validate:
    • tsc passes; no any leaks in params.
    • No string event sends.
    • No v4 forbidden imports or config keys (cond, withContext, withConfig, etc.).
    • Prefer waitFor for async actor tests and xstate/graph (createTestModel, getShortestPaths, etc.) for model/graph-driven test generation.

When A Monolith Is Acceptable

Use a single top-level machine only when it is a thin orchestration root that coordinates child actors or tightly coupled parallel app-shell concerns. Even then, unrelated domain logic should live in separate actors/machines and communicate via explicit events.

Review Checklist (Use When Auditing PRs)

  • All machines are setup(...).createMachine(...).
  • Machine has a clear statechart plan (states/events/transition table) or the PR description includes it.
  • No domain-smell: one machine has one responsibility or explicit orchestration scope.
  • No state-mirroring context flags without documented rationale.
  • No implementations inside types.
  • actions/guards read event fields only via params or assertEvent.
  • No interpret, Machine, withConfig, withContext, cond, send, pure, choose.
  • invoke has onError (and onDone when appropriate).
  • React usage prefers createActorContext at module scope; selectors are stable and use shallowEqual for objects.

Common Fixes

  • Event payload used inside an action:

    • Prefer: compute payload in params and make the action implementation depend on params.
    • Otherwise: add assertEvent(event, 'someType') inside the implementation.
  • v4 cond:

    • Rename to guard and move guard implementation into setup({ guards: { ... } }).
  • v4 interpret:

    • Replace with createActor(machine) and .start() where needed.

Notes

If official docs feel ambiguous, follow the rules file guidance: confirm behavior against XState v5 source, issues/discussions, or a minimal reproduction/type-test rather than guessing.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Funnel Builder

Builds complete multi-channel revenue funnels adapted to any business model. Combines proven frameworks from elite operators: Yomi Denzel's viral top-of-funn...

Registry SourceRecently Updated
Coding

Decode

Decode - command-line tool for everyday use

Registry SourceRecently Updated
Coding

Wip Release

One-command release pipeline. Bumps version, updates changelog + SKILL.md, publishes to npm + GitHub.

Registry SourceRecently Updated
Coding

Wip Ai Devops Toolbox Private

Complete DevOps toolkit for AI-assisted software development. Release pipeline, license compliance, copyright enforcement, repo visibility guard, identity fi...

Registry SourceRecently Updated