xstate-helper
XState v5 state machines, statecharts, and the actor model for complex, event-driven application logic. When user works with XState, state machines, statecharts, actors, createMachine/setup, createActor, @xstate/react, @xstate/store, useMachine/useActor/useSelector, invoke/spawn, guards/actions/assign, or needs to model complex async flows, wizards, and event-driven state.
What this skill does
# XState Helper Agent
Guidance for **XState v5** — state machines, statecharts, and actors. This monorepo uses Bun and a
strict ESLint config; read the **Monorepo Gotchas** section before writing any machine here — the
documented `setup({ types })` idiom from the official docs **does not lint-pass in this repo** and
there is a specific workaround.
## Versions (verified mid-2026)
| Package | Latest | Notes |
|---|---|---|
| `xstate` | 5.32.0 | Core. In-repo use: `discord-plays-pokemon/.../backend` pins `^5.32.0` |
| `@xstate/react` | 6.1.0 | React hooks. peer: `react 16.8–19`, `xstate ^5.28` |
| `@xstate/store` | 4.1.0 | Lightweight store (Zustand-like). React binding is a **separate** package |
| `@xstate/store-react` | 2.0.0 | React binding for store v4 — `@xstate/store/react` was **removed** in v4 |
```bash
bun add xstate # core
bun add @xstate/react # React bindings
bun add @xstate/store @xstate/store-react # lightweight store + its React binding
```
Requires **TypeScript 5.0+**. In `tsconfig.json` set `"strictNullChecks": true` (required — types
break without it) and `"skipLibCheck": true` (recommended).
## What's New in XState v5 (2024–2026)
v5 made **actors first-class**, removed v4 magic (typegen, implicit string events, external-by-default
transitions), and added `setup()` for type safety. Recent 5.x minors:
- **`actor.select(selector, eq?)`** (5.29) — derive a framework-agnostic `Readable<T>` off any actor's
snapshot; `.get()` + `.subscribe()` (only fires when the selected value changes). No hook needed.
- **Routable states** (5.28) — a state with `route: {}` + explicit `id` is reachable from anywhere via
`{ type: 'xstate.route', to: '#id' }`. Routes accept a `guard` (string ref resolved since 5.31.1).
- **`getMicrosteps()` / `getInitialMicrosteps()`** (5.27) — pure `[snapshot, actions]` tuples per
microstep, so you can inspect every intermediate (incl. `always`) state without executing actions.
- **`maxIterations`** (5.31) — infinite-loop guard for eventless transitions (default `Infinity`).
- **`filterEvents`** (5.30) — `xstate/graph` + `createTestModel` traversal limited to currently-enabled
events (e.g. `state.can(event)`).
- **`setup().assign/raise/sendTo/emit/spawnChild/enqueueActions(...)`** (5.22) — type-bound action
helpers; no inline generics. Current best practice for typed `enqueueActions`/`emit`/`spawnChild`.
- **`setup.extend()`** (5.24) and **`setup().createStateConfig()`** (5.21) — composable, modular setups.
- **`mapState(snapshot, mapper)`** (5.31), **`getNextTransitions(snapshot)`** (5.26), partial descriptors
in `assertEvent(event, 'FEEDBACK.*')` (5.25).
- **Model-based testing moved into core** — import from `xstate/graph` (`@xstate/test` is deprecated).
- **`@xstate/store` v3/v4** — `store.trigger.someEvent(...)` typed API, `.with(persist({ name }))`,
atoms, schema validation; v4 split framework bindings into `@xstate/store-react` etc.
## Core Mental Model
> **Everything is an actor.** An actor is a live entity with private state, a mailbox (one event at a
> time), and async message passing. A state machine is the most robust way to describe an actor's
> behavior — but `fromPromise`, `fromCallback`, `fromObservable`, and `fromTransition` are actor logic too.
- **Logic** (the machine/definition) is inert. `createActor(logic)` makes a **running actor**.
- `actor.send({ type })` — events are **objects**, never bare strings (v4 allowed strings; v5 does not).
- `actor.getSnapshot()` reads current state; `actor.subscribe(fn)` observes changes (does **not** emit
the current value immediately — read `getSnapshot()` for that).
```ts
import { createMachine, createActor } from "xstate";
const toggleMachine = createMachine({
id: "toggle",
initial: "inactive",
states: {
inactive: { on: { toggle: { target: "active" } } },
active: { on: { toggle: { target: "inactive" } } },
},
});
const actor = createActor(toggleMachine);
actor.subscribe((snapshot) => console.log(snapshot.value));
actor.start(); // logs "inactive"
actor.send({ type: "toggle" }); // logs "active"
actor.stop();
```
### Machine vs Store — pick the right tool
- **State machine (`xstate`)** — many discrete states, guarded transitions, hierarchy/parallelism, async
orchestration. Use when *the transitions are the hard part* (wizards, checkout, auth, media players).
- **Store (`@xstate/store`)** — a small, event-based shared-state container (Zustand-like) when you do
**not** need statecharts. See `references/xstate-store.md`.
- **Not XState at all** — for a bag of independent values prefer Zustand/Jotai; for server cache use
TanStack Query. A localized form/wizard reducer can just be `useReducer`. XState earns its weight when
illegal states and complex transitions are the actual problem (it makes impossible states
unrepresentable). Don't reach for it to hold three booleans.
## setup() + createMachine — the typed entry point
`setup({ types, actors, guards, actions, delays })` registers named implementations and types, then
`.createMachine(config)` references them by string. This is the v5 idiom (v5 has **no typegen**).
```ts
import { setup, assign, fromPromise } from "xstate";
// ⚠️ In THIS repo, do NOT inline `context: {} as Ctx` — see Monorepo Gotchas below.
const machine = setup({
types: {
context: {} as { count: number; user: string | undefined },
events: {} as { type: "inc"; by: number } | { type: "reset" } | { type: "LOAD" },
input: {} as { start: number },
},
actors: {
loadUser: fromPromise(async ({ input }: { input: { id: number } }) => {
const res = await fetch(`/api/users/${input.id}`);
return res.json();
}),
},
guards: {
canInc: ({ context }) => context.count < 100,
},
actions: {
increment: assign({ count: ({ context, event }) => {
// narrow the event to access its payload safely
return event.type === "inc" ? context.count + event.by : context.count;
} }),
},
}).createMachine({
id: "counter",
initial: "idle",
context: ({ input }) => ({ count: input.start, user: undefined }),
states: {
idle: {
on: {
inc: { guard: "canInc", actions: "increment" },
reset: { actions: assign({ count: 0 }) },
LOAD: { target: "loading" },
},
},
loading: {
invoke: {
src: "loadUser",
input: { id: 1 },
onDone: { target: "idle", actions: assign({ user: ({ event }) => event.output.name }) },
onError: { target: "idle" },
},
},
},
});
const actor = createActor(machine, { input: { start: 0 } }).start();
actor.send({ type: "inc", by: 5 });
```
Override implementations per instance with `machine.provide({ actions, actors, guards, delays })`
(replaces v4 `withConfig`). See `references/actors-and-machines.md` for the full machine/actor surface
(hierarchical & parallel states, history, `after`, `always`, `raise`, `enqueueActions`, `emit`, spawn).
## Context, Actions & Guards (quick reference)
```ts
import { assign, raise, sendTo, enqueueActions, and, or, not, stateIn, assertEvent } from "xstate";
// assign — object or function form; context is immutable, only assign mutates it
assign({ count: ({ context }) => context.count + 1 });
assign(({ context, event }) => ({ count: context.count + 1 }));
// raise — send an event to SELF (optionally delayed)
raise({ type: "RETRY" }, { delay: 1000 });
// sendTo — send to another actor by id, ref, or resolver
sendTo("childId", { type: "PING" });
sendTo(({ context }) => context.someRef, { type: "PING" });
// enqueueActions — replaces v4 choose/pure; imperative composition of actions
enqueueActions(({ context, enqueue, check }) => {
enqueue.assign({ count: context.count + 1 });
if (check({ type: "someGuard" })) enqueue("namedAction");
enqueue.sendTo("childId", { type: "GO" });
});
// guards — string ref, params, or higher-order combinators
guard: and(["isValid", or(["isAdmin", Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.