XState

Frameworks & Libraries

State machines and statecharts for application logic in JavaScript.

Latest 5.32.5 · by StatelyWebsitestatelyai/xstate

Release activity

Release activity — 36 releases across 30 days in the last year. Each cell is one day; darker means more releases that day. Older weeks are hidden at this screen width.
MayJunJulAug
SundayNo releases on Apr 26, 2026No releases on May 3, 20261 release on May 10, 2026No releases on May 17, 2026No releases on May 24, 2026No releases on May 31, 2026No releases on Jun 7, 2026No releases on Jun 14, 20261 release on Jun 21, 20261 release on Jun 28, 2026No releases on Jul 5, 2026No releases on Jul 12, 2026No releases on Jul 19, 2026No releases on Jul 26, 2026No releases on Aug 2, 2026No releases on Aug 9, 2026
Monday1 release on Apr 27, 2026No releases on May 4, 2026No releases on May 11, 2026No releases on May 18, 2026No releases on May 25, 2026No releases on Jun 1, 2026No releases on Jun 8, 2026No releases on Jun 15, 20261 release on Jun 22, 20261 release on Jun 29, 2026No releases on Jul 6, 2026No releases on Jul 13, 2026No releases on Jul 20, 2026No releases on Jul 27, 2026No releases on Aug 3, 2026No releases on Aug 10, 2026
TuesdayNo releases on Apr 28, 2026No releases on May 5, 2026No releases on May 12, 2026No releases on May 19, 2026No releases on May 26, 2026No releases on Jun 2, 2026No releases on Jun 9, 2026No releases on Jun 16, 20262 releases on Jun 23, 20261 release on Jun 30, 2026No releases on Jul 7, 20261 release on Jul 14, 2026No releases on Jul 21, 2026No releases on Jul 28, 2026No releases on Aug 4, 2026No releases on Aug 11, 2026
WednesdayNo releases on Apr 29, 2026No releases on May 6, 2026No releases on May 13, 2026No releases on May 20, 20261 release on May 27, 2026No releases on Jun 3, 2026No releases on Jun 10, 2026No releases on Jun 17, 20262 releases on Jun 24, 20263 releases on Jul 1, 2026No releases on Jul 8, 2026No releases on Jul 15, 2026No releases on Jul 22, 2026No releases on Jul 29, 2026No releases on Aug 5, 2026
ThursdayNo releases on Apr 30, 2026No releases on May 7, 2026No releases on May 14, 2026No releases on May 21, 2026No releases on May 28, 2026No releases on Jun 4, 2026No releases on Jun 11, 2026No releases on Jun 18, 20262 releases on Jun 25, 20262 releases on Jul 2, 2026No releases on Jul 9, 2026No releases on Jul 16, 2026No releases on Jul 23, 2026No releases on Jul 30, 2026No releases on Aug 6, 2026
FridayNo releases on May 1, 2026No releases on May 8, 2026No releases on May 15, 2026No releases on May 22, 2026No releases on May 29, 2026No releases on Jun 5, 20261 release on Jun 12, 2026No releases on Jun 19, 20261 release on Jun 26, 2026No releases on Jul 3, 2026No releases on Jul 10, 2026No releases on Jul 17, 2026No releases on Jul 24, 2026No releases on Jul 31, 2026No releases on Aug 7, 2026
SaturdayNo releases on May 2, 2026No releases on May 9, 2026No releases on May 16, 2026No releases on May 23, 2026No releases on May 30, 2026No releases on Jun 6, 2026No releases on Jun 13, 20261 release on Jun 20, 20261 release on Jun 27, 2026No releases on Jul 4, 2026No releases on Jul 11, 2026No releases on Jul 18, 2026No releases on Jul 25, 2026No releases on Aug 1, 2026No releases on Aug 8, 2026

36 releases in the last year, busiest day 3

Changelog

5.32.5

Fixed 1
  • Sending an event to a stopped actor no longer throws when the event contains unserializable data; the warning is now emitted safely regardless of the event's contents
Patch Changes
  • #5603 345e04c Thanks @xianjianlf2! - Sending an event to a stopped actor no longer throws when the event contains unserializable data (e.g. circular references). Previously, the development-only warning that an event was sent to a stopped actor used JSON.stringify on the event, which could throw and mask the intended warning. The warning is now emitted safely regardless of the event's contents.
View originalPermalink
How 5.32.5 went

5.32.4

Fixed 1
  • Fixed a bug where targeting a history state that is a direct child of a parallel state would silently do nothing when that parallel state had not been visited yet and the history state had no default target; the machine now enters the parallel state's initial configuration, matching the behavior of history states inside compound states
Patch Changes
  • #5589 e913eeb Thanks @spokodev! - Fixed a bug where targeting a history state that is a direct child of a parallel state would silently do nothing when that parallel state had not been visited yet and the history state had no default target. The machine now enters the parallel state's initial configuration, matching the behavior of history states inside compound states.

    const machine = createMachine({
      initial: 'off',
      states: {
        off: { on: { GO: 'on.hist' } },
        on: {
          type: 'parallel',
          states: {
            regA: { initial: 'a1', states: { a1: {}, a2: {} } },
            regB: { initial: 'b1', states: { b1: {}, b2: {} } },
            hist: { type: 'history', history: 'deep' }
          }
        }
      }
    });
    
    const actor = createActor(machine).start();
    actor.send({ type: 'GO' });
    actor.getSnapshot().value; // { on: { regA: 'a1', regB: 'b1' } }
    
View originalPermalink
How 5.32.4 went

6.0.0-alpha.16

Pre-release
Added 1
  • Add state-level onError transitions for handling xstate.error.* events, with the caught error available on event.error
Changed 1
  • Allow machines with no external events to be used anywhere AnyActorLogic or AnyStateMachine is expected
Minor Changes
  • e410f24: Add state-level onError transitions for handling xstate.error.* events.

    State onError catches actor, execution, and communication errors while the state is active. The caught error is available on event.error.

    const machine = createMachine({
      initial: 'active',
      states: {
        active: {
          onError: ({ event }) => ({
            target: 'failed',
            context: {
              message:
                event.error instanceof Error
                  ? event.error.message
                  : String(event.error)
            }
          })
        },
        failed: {}
      }
    });
    
Patch Changes
  • f6edec7: Allow machines with no external events to be used anywhere AnyActorLogic or AnyStateMachine is expected.

    const machine = setup({
      schemas: {
        events: {}
      }
    }).createMachine({});
    
    const logic: AnyActorLogic = machine;
    const anyMachine: AnyStateMachine = machine;
    

    Machines with empty event schemas still reject external events sent to their actors.

View originalPermalink
How 6.0.0-alpha.16 went

5.32.3

Fixed 2
  • Fixed initialTransition and transition throwing "Actor with system ID '...' already exists" error when the machine contains an invoke with a systemId by replacing the actor's system reference with a freshly-created system in createInertActorScope
  • Add missing https:// protocol to the Stately Studio link in the README
Patch Changes
  • #5575 830db8b Thanks @JSap0914! - Fixed initialTransition (and transition) throwing "Actor with system ID '...' already exists" when the machine contains an invoke with a systemId.

    Root cause: createInertActorScope used createActor(logic) internally, which eagerly ran getInitialSnapshot during construction and registered any systemId-carrying child actors in the system. When the caller then ran getInitialSnapshot (or transition) via the returned scope, the same system was reused, causing the duplicate-registration error.

    Fix: After creating the internal actor, createInertActorScope now replaces the actor's system reference with a freshly-created system. Child actors spawned by the subsequent caller-driven getInitialSnapshot / transition invocation therefore register into a clean system with no pre-existing entries.

    const machine = createMachine({
      initial: 'idle',
      states: {
        idle: {
          invoke: {
            src: fromPromise(async () => 42),
            systemId: 'myActor' // previously caused: "Actor with system ID 'myActor' already exists"
          }
        }
      }
    });
    
    // Now works correctly — returns [snapshot, actions] without throwing
    const [snapshot, actions] = initialTransition(machine);
    
  • #5585 a551a2b Thanks @RubenFricke! - Add missing https:// protocol to the Stately Studio link in the README

View originalPermalink
How 5.32.3 went

6.0.0-alpha.15

Pre-release
Added 2
  • Setup-bound invoke transition callbacks now validate target state context requirements for onDone, onError, onSnapshot, and onTimeout
  • onDone callback now infers output from the invoked actor logic
Patch Changes
  • 37d3254: Setup-bound invoke transition callbacks now validate target state context requirements for onDone, onError, onSnapshot, and onTimeout. onDone also infers output from the invoked actor logic.

    import { createAsyncLogic, setup } from 'xstate';
    import { z } from 'zod';
    
    const machine = setup({
      actorSources: {
        loadUser: createAsyncLogic({
          run: async () => ({ name: 'Ada' })
        })
      },
      states: {
        loading: {},
        success: {
          schemas: {
            context: z.object({
              user: z.object({ name: z.string() })
            })
          }
        }
      }
    }).createMachine({
      context: {},
      initial: 'loading',
      states: {
        loading: {
          invoke: {
            src: 'loadUser',
            // Type-safe return value for invoke callbacks
            onDone: ({ event }) => ({
              target: 'success',
              context: { user: event.output }
            })
          }
        },
        success: {}
      }
    });
    
View originalPermalink
How 6.0.0-alpha.15 went

6.0.0-alpha.14

Pre-release
Added 1
  • Add a path-bound overload to `setup(...).createStateConfig(path, config)` that binds the config to a specific setup-declared state by dotted path and narrows the input schema accordingly
Changed 1
  • Make `StateFrom<typeof machine>` preserve the machine's concrete state value
Fixed 2
  • Fix `snapshot.matches(...)` narrowing so repeated checks like `snapshot.matches('loaded') || snapshot.matches('failed')` compile correctly
  • Fix function-syntax transitions to pass `input` to target state entry actions
Minor Changes
  • c0c21d0: Add a path-bound overload to setup(...).createStateConfig(path, config).

    When a state declares its own input schema, the anonymous createStateConfig(config) form types input as a broad union across all states. This makes the resulting config incompatible with the specific state it's meant for — assigning it inside createMachine produces a type error because the input types don't match.

    The new createStateConfig(path, config) overload binds the config to a specific setup-declared state by dotted path (e.g. 'loading' or 'parent.child'). The addressed state's own input schema is used inside entry/exit args, and bare transition targets are validated against the state's siblings.

    const s = setup({
      states: {
        idle: {},
        active: {
          schemas: { input: z.object({ userId: z.string() }) }
        }
      }
    });
    
    // Before: anonymous form — `input` is typed broadly, and assigning this
    // config to the `active` state in createMachine fails with a type error.
    const active = s.createStateConfig({
      entry: ({ input }) => {
        // input is not narrowed to { userId: string }
      }
    });
    
    // After: path-bound form — `input` is narrowed to `active`'s own schema.
    const active = s.createStateConfig('active', {
      entry: ({ input }) => {
        input.userId; // string
      }
    });
    
    // Works for nested states too:
    const child = s.createStateConfig('parent.child', { ... });
    
Patch Changes
  • 8e3cce6: Fix snapshot.matches(...) narrowing so repeated checks like snapshot.matches('loaded') || snapshot.matches('failed') compile correctly, and make StateFrom<typeof machine> preserve the machine's concrete state value.

  • 0c2a6e5: Fix function-syntax transitions not passing input to target state entry actions.

    on: {
      FETCH: ({ context, event }) => ({
        target: 'fetching',
        input: { url: event.url, token: context.authToken }
      });
    }
    

    Previously, input returned from function-syntax transitions was silently ignored. Now it is correctly forwarded to the target state's entry action.

View originalPermalink
How 6.0.0-alpha.14 went

6.0.0-alpha.13

Pre-release
Added 1
  • Added `createFSM(...)` for flat, actor-compatible finite state machines with support for object transitions, function transitions, `enq` actions, initial input, state `input`, entry actions, and exit actions
Changed 1
  • Object transition configs now support dynamic context patches with a `context` mapper function
Minor Changes
  • bdc54dd: Added createFSM(...) for flat, actor-compatible finite state machines.

    import { createActor, createFSM } from 'xstate';
    
    const toggleLogic = createFSM({
      initial: 'inactive',
      context: { count: 0 },
      states: {
        inactive: {
          on: {
            toggle: {
              target: 'active',
              context: { count: 1 }
            }
          }
        },
        active: {
          on: {
            toggle: ({ context }, enq) => {
              enq(() => console.log('toggled'));
    
              return {
                target: 'inactive',
                context: { count: context.count + 1 }
              };
            }
          }
        }
      }
    });
    
    const actor = createActor(toggleLogic).start();
    
    actor.send({ type: 'toggle' });
    

    createFSM(...) supports XState-style object transitions, function transitions, enq actions, initial input, state input, entry actions, and exit actions. Plain string targets are intentionally not supported; use object targets such as { target: 'active' }.

    Simple FSM transitions preserve immutable public snapshots while using structural sharing and a lighter transition path for common { target }, { context }, and { target, context } transitions.

Patch Changes
  • e297115: Object transition configs now support dynamic context patches with a context mapper.

    onDone: {
      target: 'done',
      context: ({ context, output }) => ({
        answer: output,
        memory: [...context.memory, output]
      })
    }
    
View originalPermalink
How 6.0.0-alpha.13 went

6.0.0-alpha.12

Pre-release
Changed 5
  • serializeMachine(...) and createMachineFromConfig(...) now represent inline functions as { '@code': string, '@lang': 'ts' } and omit non-portable values such as actor logic, runtime schemas, class instances, symbols, and bigints from serialized JSON
  • void and undefined are now accepted as type-only schemas
  • Async logic output is now inferred from an input-only schema
  • Actions and guards can now be typed via schemas
  • trigger is now correctly typed on spawned actors
Fixed 1
  • Spawning a child with enq.spawn(...) from a transition function now creates and starts the child actor exactly once for the committed transition
Patch Changes
  • 4d9ba1c: Spawning a child with enq.spawn(...) from a transition function now creates and starts the child actor exactly once for the committed transition.

    const machine = createMachine({
      on: {
        spawn: (_, enq) => {
          enq.spawn(childMachine, { registryKey: 'child' });
        }
      }
    });
    
  • 6798cb1: serializeMachine(...) and createMachineFromConfig(...) now represent inline functions (guards, actions, transitions, delays, route functions) as { '@code': string, '@lang': 'ts' }. Non-portable values such as actor logic, runtime schemas, class instances, symbols, and bigints are omitted from the serialized JSON.

    import { serializeMachine } from 'xstate';
    
    serializeMachine(machine);
    // inline functions → { '@code': '() => true', '@lang': 'ts' }
    

    Type-only refinements: void and undefined are accepted as type-only schemas, async logic output is inferred from an input-only schema, actions/guards can be typed via schemas, and trigger is correctly typed on spawned actors.

View originalPermalink
How 6.0.0-alpha.12 went

6.0.0-alpha.11

Pre-release
Changed 1
  • Machines that declare schemas.output now type-check top-level final state output values against the machine output type
Patch Changes
  • 57e8d85: Machines that declare schemas.output now type-check top-level final state output values against the machine output type.

    createMachine({
      schemas: {
        output: types<{ status: 'ok' }>()
      },
      initial: 'done',
      states: {
        done: {
          type: 'final',
          output: { status: 'ok' }
        }
      },
      output: ({ event }) => event.output
    });
    
View originalPermalink
How 6.0.0-alpha.11 went

6.0.0-alpha.10

Pre-release
Fixed 1
  • Export setup system helper types used by public machine types to avoid inferred machine types referring to internal declaration paths when setup(...) includes a typed system registry
Patch Changes
  • 86b43ea: Export setup system helper types used by public machine types.

    This avoids inferred machine types referring to internal declaration paths when setup(...) includes a typed system registry.

View originalPermalink
How 6.0.0-alpha.10 went

6.0.0-alpha.9

Pre-release
Added 1
  • Export setup helper types AnySetupConfig and SetupReturnFromConfig for libraries that return or decorate setup-bound objects while preserving native setup(...).createMachine(...) typing
Changed 1
  • Registered invoke onDone callbacks now receive the invoked actor's output type, and machine.provide({ actorSources }) accepts compatible actor implementations with sound input/output variance
Fixed 1
  • Empty Standard Schema event objects now infer as type-only events, so { type: 'SEND' } is accepted for an empty SEND payload schema while non-empty schemas still require their payload fields
Minor Changes
  • 54205cc: Export setup helper types for libraries that return or decorate setup-bound objects while preserving native setup(...).createMachine(...) typing.

    import {
      setup,
      type AnySetupConfig,
      type SetupReturnFromConfig
    } from 'xstate';
    
    function decorateSetup<const TConfig extends AnySetupConfig>(
      config: TConfig
    ): SetupReturnFromConfig<TConfig> & { extra: true } {
      const s = setup(config) as SetupReturnFromConfig<TConfig>;
    
      return Object.assign(s, { extra: true as const });
    }
    
Patch Changes
  • 54205cc: Empty Standard Schema event objects now infer as type-only events, so { type: 'SEND' } is accepted for an empty SEND payload schema while non-empty schemas still require their payload fields.
  • 54205cc: Registered invoke onDone callbacks now receive the invoked actor's output type, and machine.provide({ actorSources }) accepts compatible actor implementations with sound input/output variance.
View originalPermalink
How 6.0.0-alpha.9 went

6.0.0-alpha.8

Pre-release
Changed 1
  • Done transitions now receive `output` directly in callback arguments for XState done events
Patch Changes
  • 667d1c7: Done transitions now receive output directly in callback arguments.

    invoke: {
      src: fetchUser,
      onDone: ({ output }) => {
        output.name;
      }
    }
    

    The direct output value is only provided for XState done events, such as xstate.done.actor.* and xstate.done.state.*.

View originalPermalink
How 6.0.0-alpha.8 went

6.0.0-alpha.7

Pre-release
Added 2
  • Add `createSystem({ registry })` for declaring typed actor registry keys and creating actors in that system, with registry keys assigned via `registryKey` on invokes, spawned actors, and root actors
  • Static transition config objects may now include a shallow `context` patch that is shallow-merged with the current context
Major Changes
  • 89895f9: Add createSystem({ registry }) for declaring typed actor registry keys and creating actors in that system.

    Registry keys are assigned with registryKey on invokes, spawned actors, and root actors created from the system. Registry keys are checked against the declared registry when using createSystem.

    const system = createSystem({
      registry: {
        receiver: receiverLogic
      }
    });
    
    const machine = system.setup().createMachine({
      invoke: {
        src: receiverLogic,
        registryKey: 'receiver'
      }
    });
    
    const actor = system.createActor(machine).start();
    
    system.get('receiver')?.send({ type: 'HELLO' });
    
Patch Changes
  • 89895f9: Static transition config objects may now include a shallow context patch.

    createMachine({
      context: { draftAnyway: false, count: 0 },
      initial: 'idle',
      states: {
        idle: {
          on: {
            DRAFT_ANYWAY: {
              target: 'drafting',
              context: { draftAnyway: true }
            }
          }
        },
        drafting: {}
      }
    });
    

    The patch is shallow-merged with the current context, just like context returned from a transition function. Setup-typed machines still require any keys needed by the target state's narrowed context.

View originalPermalink
How 6.0.0-alpha.7 went

6.0.0-alpha.6

Pre-release
Changed 2
  • State transition functions now type `enq` with the machine's events and emitted events
  • Transition functions may now return only a target when the target state's context is compatible with the current context
Patch Changes
  • ecd97db: State transition functions now type enq with the machine's events and emitted events.

    setup({
      schemas: {
        events: {
          go: types<{}>()
        }
      }
    }).createMachine({
      states: {
        active: {
          on: {
            go: (_args, enq) => {
              enq.raise({ type: 'go' });
            }
          }
        }
      }
    });
    
  • 4b5b14f: Transition functions may now return only a target when the target state's context is compatible with the current context.

    setup({
      schemas: {
        context: types<{ count: number }>(),
        events: {
          next: types<{}>()
        }
      }
    }).createMachine({
      context: { count: 0 },
      initial: 'idle',
      states: {
        idle: {
          on: {
            next: () => ({ target: 'done' })
          }
        },
        done: {}
      }
    });
    
View originalPermalink
How 6.0.0-alpha.6 went

6.0.0-alpha.5

Pre-release
Removed 1
  • String target shorthand is no longer accepted for transition configs; use the object form with `target` property instead
Major Changes
  • 297f851: String target shorthand is no longer accepted for transition configs. Use the object form with target instead:

    createMachine({
      initial: 'idle',
      states: {
        idle: {
          on: {
            start: { target: 'active' }
          }
        },
        active: {}
      }
    });
    
View originalPermalink
How 6.0.0-alpha.5 went

6.0.0-alpha.4

Pre-release
Added 2
  • Add schemas.children for explicitly typing child actor refs by child ID with declared child refs type, child snapshots, and invoke configs
  • Add isBuiltInExecutableAction(...) to narrow executable effects to XState's built-in effect union for declarative inspection
Changed 3
  • Actor logic now returns effects from both regular and initial transitions, with transition(...) and initialTransition(...) returning [snapshot, effects] tuples
  • Built-in executable effects now expose stable named metadata fields accessible via effect.type including @xstate.start, @xstate.sendTo, @xstate.raise, and @xstate.stop
  • fromStore(...) effects now run after the actor snapshot is committed so effect callbacks read the updated snapshot from enqueue.getSnapshot()
Major Changes
  • c3f7a9d: Actor logic now returns effects from both regular and initial transitions.

    Hand-written actor logic should return [snapshot, effects] from transition(...) and provide initialTransition(...) for creating the initial [snapshot, effects] tuple. getInitialSnapshot(...) remains available for snapshot-only reads.

    const logic = {
      transition: (snapshot, event) => [snapshot, []],
      initialTransition: (input, _scope) => [
        {
          status: 'active',
          output: undefined,
          error: undefined,
          input
        },
        []
      ],
      getInitialSnapshot: (scope, input) =>
        logic.initialTransition(input, scope)[0]
    };
    

    transition(...) and initialTransition(...) continue to return [snapshot, actions] for machine logic.

    fromStore(...) effects now run after the actor snapshot is committed, so effect callbacks read the updated snapshot from enqueue.getSnapshot().

  • 309b106: Add schemas.children for explicitly typing child actor refs by child ID. Declared child refs type children.someId, child snapshots, and invoke configs so invoke: { id: 'someId', src } must match the declared child actor contract.

  • fa2bbf0: Built-in executable effects returned from transition(...) and initialTransition(...) are now easier to inspect declaratively.

    Use isBuiltInExecutableAction(effect) to narrow an executable effect to XState's built-in effect union, then switch on effect.type to access stable, named metadata fields:

    const [snapshot, effects] = initialTransition(machine);
    
    for (const effect of effects) {
      if (!isBuiltInExecutableAction(effect)) {
        continue;
      }
    
      switch (effect.type) {
        case '@xstate.start':
          effect.id;
          effect.logic;
          effect.src;
          effect.input;
          break;
    
        case '@xstate.sendTo':
          effect.target;
          effect.event;
          effect.delay;
          break;
    
        case '@xstate.raise':
          effect.event;
          effect.delay;
          break;
      }
    }
    

    The built-in stop effect is now exposed as @xstate.stop, matching @xstate.start.

View originalPermalink
How 6.0.0-alpha.4 went

5.32.2

Fixed 1
  • Fall back to wildcard event descriptors when an exact event descriptor's guard fails, allowing wildcard transitions to be considered if exact match guards fail
Patch Changes
  • #5548 8a53531 Thanks @JSap0914! - fix(core): fall back to wildcard event descriptors when an exact descriptor's guard fails

    When a state has both an exact event descriptor (e.g. "foo.bar") and a matching wildcard descriptor (e.g. "foo.*"), transitions from the exact descriptor are now tried first; if all their guards fail, matching wildcard descriptor transitions are tried as fallback. Previously, the presence of an exact match would prevent any wildcard fallback from being considered, leaving the machine in its current state when the exact descriptor's guard failed.

View originalPermalink
How 5.32.2 went

6.0.0-alpha.3

Pre-release
Added 1
  • Expose machine.schemas as a public runtime-readable schema contract
Changed 1
  • Serialize function implementations as portable code expressions in guards, actions, delays, and inline machine config
Minor Changes
  • #44 0a883ad Thanks @pull! - Expose machine.schemas as a public runtime-readable schema contract.

    const machine = createMachine({
      schemas: {
        context: z.object({ count: z.number() }),
        events: {
          inc: z.object({ by: z.number() })
        }
      },
      context: { count: 0 }
    });
    
    machine.schemas?.events?.inc;
    
Patch Changes
  • #44 d95287b Thanks @pull! - Serialize function implementations as portable code expressions.

    Functions in guards, actions, delays, and inline machine config now serialize as:

    { '@type': 'code', lang: 'ts', expr: '() => true' }
    

    Values that cannot be represented as code or JSON still serialize with explicit $unserializable markers.

View originalPermalink
How 6.0.0-alpha.3 went

6.0.0-alpha.2

Pre-release
Fixed 1
  • Fixed a bug where an invoked actor's input and dynamic src function received the context from before the transition that entered the invoking state, rather than the updated context
Patch Changes
  • #44 d6a537e Thanks @pull! - Fixed a bug where an invoked actor's input (and a dynamic src function) received the context from before the transition that entered the invoking state, rather than the updated context. Now, when a transition updates context and targets a state that invokes an actor, the actor's input sees the updated context — consistent with that state's entry actions.

    const machine = createMachine({
      context: { value: 0 },
      initial: 'idle',
      states: {
        idle: {
          on: {
            start: () => ({ target: 'active', context: { value: 100 } })
          }
        },
        active: {
          invoke: {
            src: asyncLogic,
            // now receives { value: 100 } instead of { value: 0 }
            input: ({ context }) => ({ val: context.value })
          }
        }
      }
    });
    
View originalPermalink
How 6.0.0-alpha.2 went

6.0.0-alpha.1

Pre-release
Added 1
  • Export checkStateIn(snapshot, '#id') helper for matching state by id
Changed 17
  • Invoked and spawned actors now start as part of the transition that creates them via an internal deferred start action instead of being started directly by actor.start()
  • Child failures that occur synchronously while starting now surface through the invoking state's onError transition instead of throwing out of actor.start()
  • Actions, guards, and transitions are now plain inline functions that receive args and an enqueue object for side effects
  • Update context by returning a partial or full context patch instead of using assign
  • Perform side effects through the enqueue object with methods: raise, sendTo, emit, log, cancel, spawn, stop, and arbitrary effects via enq(fn, ...args)
  • Guards are now plain functions that return a boolean instead of using guard creators
Removed 2
  • Remove action and guard creators: assign, raise, sendTo, sendParent, forwardTo, emit, log, cancel, spawnChild, stop, stopChild, enqueueActions, and guard creators and, or, not, stateIn
  • Remove deprecated interpret function and Interpreter type
Major Changes
  • #44 52970ea Thanks @pull! - Invoked and spawned actors are no longer started directly by actor.start(). They now start as part of the transition that creates them (via an internal deferred start action), the same way other entry effects run.

    The user-visible consequence: a child that fails synchronously while starting now surfaces that failure through the invoking state's onError transition instead of throwing out of actor.start():

    const machine = createMachine({
      initial: 'loading',
      states: {
        loading: {
          invoke: {
            src: createAsyncLogic({
              run: () => {
                throw new Error('boom'); // sync failure on start
              }
            }),
            onError: 'failed'
          }
        },
        failed: {}
      }
    });
    
    const actor = createActor(machine).start(); // does not throw
    actor.getSnapshot().value; // 'failed'
    

    Restored (rehydrated) children that were active when a snapshot was persisted are still restarted on actor.start(), so persistence behavior is unchanged.

  • #44 52970ea Thanks @pull! - Actions, guards, and transitions are now plain inline functions, and the v5 action/guard creators are removed.

    Removed exports: assign, raise, sendTo, sendParent, forwardTo, emit, log, cancel, spawnChild, stop, stopChild, enqueueActions, and the guard creators and, or, not, stateIn.

    Instead, a transition/action/guard is a function (args, enq) => ...:

    • Update context by returning a partial-or-full { context } patch (no more assign).
    • Perform side effects through the enq enqueue object: enq.raise, enq.sendTo, enq.emit, enq.log, enq.cancel, enq.spawn, enq.stop, plus enq(fn, ...args) for arbitrary effects.
    • Guards are just functions that return a boolean (or undefined/false to block).
    - import { assign, raise, sendTo, and, not } from 'xstate';
    
      const machine = createMachine({
        context: { count: 0 },
        on: {
    -     INC: {
    -       guard: and([not('isMax'), 'isReady']),
    -       actions: assign({ count: ({ context }) => context.count + 1 })
    -     }
    +     INC: ({ context, guards }) => {
    +       if (guards.isMax(context) || !guards.isReady(context)) return;
    +       return { context: { count: context.count + 1 } };
    +     }
        }
      });
    

    The stateIn guard is replaced by checking the snapshot directly — use snapshot.matches(...) inside a transition function:

    on: {
      CHECK: ({ self }) => {
        if (self.getSnapshot().matches({ b: 'b2' })) {
          return { target: 'a2' };
        }
      };
    }
    

    For matching by state id (the '#id' form, which matches() doesn't resolve), the exported checkStateIn(snapshot, '#id') helper is also available.

  • #44 52970ea Thanks @pull! - Remove the deprecated interpret function and Interpreter type. Use createActor(...) and Actor (or ActorRefFrom<...>) instead.

    - import { interpret, type Interpreter } from 'xstate';
    - const actor = interpret(machine);
    + import { createActor, type Actor } from 'xstate';
    + const actor = createActor(machine);
    
  • #44 52970ea Thanks @pull! - schemas is now the way to type a machine, replacing v5's types: {} as {...}. Each schemas field accepts any Standard Schema (Zod, Valibot, …) for both type inference and (where supported) runtime validation, or types<T>() for types only.

    Notably, schemas.events is a map of event-type → payload schema, inferred into a discriminated union keyed by type:

    import { createMachine } from 'xstate';
    import { z } from 'zod';
    
    const machine = createMachine({
      schemas: {
        context: z.object({ count: z.number() }),
        events: {
          inc: z.object({ by: z.number() }),
          reset: z.object({})
        },
        input: z.object({ start: z.number() }),
        output: z.object({ total: z.number() }),
        emitted: { changed: z.object({ count: z.number() }) },
        tags: z.union([z.literal('busy'), z.literal('idle')]),
        meta: z.object({ label: z.string() })
      },
      context: ({ input }) => ({ count: input.start }),
      initial: 'active',
      states: {
        active: {
          on: {
            inc: ({ context, event }) => ({
              context: { count: context.count + event.by }
            })
          }
        }
      }
    });
    
    • context → context type (literal initial values are widened, so updates typecheck).
    • events{ type: 'inc'; by: number } | { type: 'reset' }; payloads are typed on event in every transition/action/guard function.
    • input → typed createActor(machine, { input }) and the context initializer argument.
    • output → typed snapshot.output.
    • emitted → typed actor.on('changed', (ev) => ev.count).
    • tags → constrains snapshot.hasTag(...).
    • meta → typed state meta.

    actors, actions, guards, and delays are top-level config keys (now inline functions), not schemas keys.

  • #44 96aee67 Thanks @pull! - Separate concrete actors from actor refs in public types. ActorRef now represents the consumer-facing contract for sending events, reading published snapshots, and listening to emitted events with actorRef.on(...); concrete Actor instances provide lifecycle and runtime capabilities and still satisfy actor ref contracts.

  • #44 52970ea Thanks @pull! - setup(...) no longer registers implementations. It now takes only { schemas?, states? } and returns { createMachine, createStateConfig, states }.

    In v5, setup({ schemas, actors, actions, guards, delays }) registered named implementations and returned action creators (assign, sendTo, raise, …). In v6, actions/guards/actors/delays are plain inline functions, so setup no longer accepts or returns them. Its job is now machine- and state-level typing: it validates state keys, initial, and transition targets against the declared states, and types per-state input/context.

    const { createMachine, createStateConfig } = setup({
      schemas: {
        context: types<{ count: number }>(),
        events: { INC: types<{ value: number }>() }
      },
      states: {
        idle: {},
        loading: { schemas: { input: z.object({ userId: z.string() }) } }
      }
    });
    

    setup().createMachine() merges setup schemas with config schemas. Bare createMachine({ schemas }) infers the same machine-level types without the state-key checks.

Minor Changes
  • #44 52970ea Thanks @pull! - Add actor.trigger — a typed event-sender proxy. actor.trigger.EVENT(payload) is shorthand for actor.send({ type: 'EVENT', ...payload }):

    actor.trigger.NEXT();
    actor.trigger.INC({ by: 5 });
    
  • #44 021cc56 Thanks @pull! - Machine JSON revival now preserves more of the serialized machine definition, including delayed transitions, state timeouts, state tags, state output, invoke input, invoke completion transitions, invoke timeouts, and implementation maps passed to createMachineFromConfig.

    const machine = createMachineFromConfig(
      {
        initial: 'loading',
        states: {
          loading: {
            invoke: {
              src: 'loadUser',
              input: { userId: '42' },
              onDone: { target: 'done' },
              timeout: 5000,
              onTimeout: { target: 'timedOut' }
            }
          },
          done: {},
          timedOut: {}
        }
      },
      {
        actors: { loadUser }
      }
    );
    

    The migration codemod now reports manual review notes for known non-rename migrations such as fromPromise(...), return assign(...), object-form actions/guards, and legacy types: {} schema declarations.

  • #44 52970ea Thanks @pull! - createLogic and createAsyncLogic gain a durable-effect enqueue API on their run function's second argument (enq).

    • enq.effect(key?, fn) registers a side effect that runs once per key (an unnamed effect runs every transition) and is cleaned up when the actor stops.
    • enq.step(key, asyncFn) (async logic) is an await-able step whose result is memoized into the persisted snapshot under snapshot.effects[key]. A rehydrated actor replays run but skips steps that already completed, so long-running async logic is resumable across persistence.
    const logic = createAsyncLogic({
      run: async (_, enq) => {
        const user = await enq.step('fetchUser', () => fetchUser());
        const order = await enq.step('createOrder', () => createOrder(user.id));
        return order.id;
      }
    });
    
    // snapshot.effects.fetchUser === { status: 'done', output: { id: 1 } }
    

    A pending step can also be resolved externally by sending { type: 'xstate.logic.effect.resolve', key, output }. The LogicEnqueue, LogicEffect, and LogicEffectState types are exported.

  • #44 52970ea Thanks @pull! - Add timeouts and duration-string delays.

    • State-level timeout / onTimeout — declare a timeout on a state that transitions when the duration elapses (and is cancelled if the state is exited first):

      states: {
        waiting: {
          timeout: 1000,
          onTimeout: 'escalated'
        },
        escalated: {}
      }
      
    • createAsyncLogic timeout — async logic can time out; when it does, the run's AbortSignal is aborted and the actor errors with a TimeoutError (exported from xstate):

      const logic = createAsyncLogic({
        timeout: '10ms',
        run: ({ signal }) => fetch('/slow', { signal })
      });
      
    • Invoke-level timeout / onTimeout — an invocation can race a timeout: if the invoked actor doesn't complete in time, the onTimeout transition is taken; if it settles first (or the state is exited), the timeout is cancelled. timeout accepts a number, a duration string, a referenced delay, or a function ({ context, event }) => duration. Both state- and invoke-level timeout throw at construction if declared without a matching onTimeout.

      working: {
        invoke: {
          src: fetchReport,
          timeout: ({ context }) => context.slaMs,
          onTimeout: 'timedOut',
          onDone: 'done'
        }
      }
      
    • Duration-string delays — delays (including after and timeout) accept human-readable strings like '10ms' and '5s', as well as ISO-8601 durations like 'PT2M', in addition to numbers:

      waiting: {
        after: {
          '5s': 'timedOut'
        }
      }
      
  • #44 d9079cd Thanks @pull! - Logic creators now accept Standard Schemas for type inference.

    createLogic(...) and createAsyncLogic(...) accept schemas.input and schemas.output:

    const loadUser = createAsyncLogic({
      schemas: {
        input: z.object({ userId: z.string() }),
        output: z.object({ name: z.string() })
      },
      run: async ({ input }) => {
        input.userId; // string
    
        return {
          name: 'David'
        };
      }
    });
    

    The schemas are type-only for now. Runtime validation will be added later as an opt-in behavior.

    createCallbackLogic(...), createObservableLogic(...), and createEventObservableLogic(...) also accept schemas.input with object-form config:

    const logic = createCallbackLogic({
      schemas: {
        input: z.object({ userId: z.string() })
      },
      run: ({ input }) => {
        input.userId; // string
      }
    });
    
  • #44 52970ea Thanks @pull! - Add createStateConfig(...) — author a standalone, fully-typed state node config (with schemas) that can be composed into a machine, mirroring how setup(...).createMachine(...) infers types.

    import { createStateConfig } from 'xstate';
    
    const loading = createStateConfig({
      on: {
        RESOLVE: 'success'
      }
    });
    

    This is the building block for authoring machines as plain data: a createStateConfig node is a typed, serializable config object you compose into a machine — useful for data-first / JSON-driven state machines (round-tripping with serializeMachine/createMachineFromConfig) while keeping per-state schema typing.

  • #44 52970ea Thanks @pull! - Add enq.listen and enq.subscribeTo for subscribing to other actors from inside transition/action functions.

    • enq.listen(ref, eventType, mapper) subscribes to events emitted by another actor (supports wildcards like 'data.*') and relays a mapped event back to the current actor.
    • enq.subscribeTo(ref, mappers) subscribes to another actor's snapshot/done/error (pass { snapshot, done, error }, or a single function as snapshot shorthand). It also accepts an atom, in which case the mapper receives the atom's current value.

    Both return a stoppable child ref (enq.stop(ref)) and are torn down automatically when the parent stops. The underlying logic creators createListenerLogic and createSubscriptionLogic are exported.

    entry: (_, enq) => {
      const child = enq.spawn(childLogic, { id: 'child' });
      enq.listen(child, 'data.*', (ev) => ({ type: 'DATA', value: ev.value }));
      enq.subscribeTo(child, {
        done: (output) => ({ type: 'CHILD_DONE', output })
      });
    };
    
  • #44 52970ea Thanks @pull! - Add internalEvents: a list of event types that may be raised from within the machine (e.g. via enq.raise(...)) but are rejected when sent to the actor from the outside.

    const machine = createMachine({
      internalEvents: ['tick'] as const,
      initial: 'idle',
      states: {
        idle: {
          on: {
            start: (_, enq) => {
              enq.raise({ type: 'tick' }); // allowed internally
            },
            tick: 'running'
          }
        },
        running: {}
      }
    });
    
    // actor.send({ type: 'tick' }) from outside is rejected
    
  • #44 021cc56 Thanks @pull! - State-level schemas.context now narrows context types for state actions, transition functions, and snapshots checked with snapshot.matches(...).

    const machine = setup({
      states: {
        idle: {
          schemas: { context: z.object({ user: z.null() }) }
        },
        success: {
          schemas: { context: z.object({ user: z.string() }) }
        }
      }
    }).createMachine({
      schemas: {
        context: z.object({ user: z.string().nullable() }),
        events: {
          LOAD: z.object({})
        }
      },
      initial: 'idle',
      context: { user: null },
      states: {
        idle: {
          on: {
            LOAD: () => ({
              target: 'success',
              context: { user: 'Ada' }
            })
          }
        },
        success: {
          entry: ({ context }) => {
            context.user; // string
          }
        }
      }
    });
    
    const actor = createActor(machine).start();
    const snapshot = actor.getSnapshot();
    
    if (snapshot.matches('success')) {
      snapshot.context.user; // string
    }
    

    State-level schemas.input is also supported: input supplied on a transition or initial ({ target, input }) is typed in that state's entry/exit and transition functions via ({ input }), read from a snapshot with snapshot.getInputs() (keyed by state node id), and typed recursively for nested states.

  • #44 52970ea Thanks @pull! - Add choice states — a state that immediately routes to a target via a resolver function, returning the first matching transition config.

    const machine = createMachine({
      context: { userStatus: 'vip' },
      initial: 'routing',
      states: {
        routing: {
          type: 'choice',
          choice: ({ context }) => {
            if (context.userStatus === 'vip') return { target: 'vipFlow' };
            return { target: 'standardFlow' };
          }
        },
        vipFlow: {},
        standardFlow: {}
      }
    });
    

    A choice state must declare a choice function and must resolve to a target, and may not declare entry/exit/on/after/invoke — these throw at construction.

View originalPermalink
How 6.0.0-alpha.1 went

5.32.1

Fixed 1
  • Resolve children snapshot union pollution for typed invoke
Patch Changes
View originalPermalink
How 5.32.1 went

5.31.1

Fixed 1
  • Fixed route transition guards so named guards registered with setup({ guards }) are resolved for route.guard
Patch Changes
  • #5525 f79ea13 Thanks @davidkpiano! - Fixed route transition guards so named guards registered with setup({ guards }) are resolved for route.guard.

    const machine = setup({
      guards: {
        isReady: ({ context }) => context.ready
      }
    }).createMachine({
      states: {
        review: {
          id: 'review',
          route: {
            guard: 'isReady'
          }
        }
      }
    });
    
View originalPermalink
How 5.31.1 went

5.31.0

Added 2
  • Add mapState(snapshot, mapper) to map a snapshot to values based on active state(s)
  • Add maxIterations option to configure the maximum number of microsteps allowed before throwing an infinite loop error
Minor Changes
  • #5429 9d9c1fe Thanks @davidkpiano! - Add mapState(snapshot, mapper) to map a snapshot to values based on active state(s).

    import { mapState } from 'xstate';
    
    const results = mapState(snapshot, {
      states: {
        loading: { map: () => 'Loading...' },
        success: { map: (snap) => snap.context.data },
        error: { map: (snap) => snap.context.error.message }
      }
    });
    
    console.log(results);
    // E.g. if snapshot.value === 'loading', then:
    // [
    //   { stateNode: { key: 'loading' }, result: 'Loading...' }
    // ]
    
  • #5430 e543599 Thanks @davidkpiano! - Add maxIterations option to configure the maximum number of microsteps allowed before throwing an infinite loop error. The default is Infinity (no limit) to avoid breaking existing machines.

    You can configure it when creating a machine:

    const machine = createMachine({
      // ... machine config
      options: {
        maxIterations: 1000 // set a limit to enable infinite loop detection
      }
    });
    
View originalPermalink
How 5.31.0 went

5.30.0

Added 1
  • Add a filterEvents option to xstate/graph traversal helpers and createTestModel(...) to control which events should be explored from each state
Minor Changes
  • #5493 871857d Thanks @davidkpiano! - Add a filterEvents option to xstate/graph traversal helpers and createTestModel(...) to control which events should be explored from each state.

    This makes it possible to opt into enabled-only traversal for machine snapshots, such as when you only want to explore events that currently pass guards:

    import { createTestModel } from 'xstate/graph';
    
    const model = createTestModel(machine);
    
    const paths = model.getSimplePaths({
      filterEvents: (state, event) => state.can(event)
    });
    
View originalPermalink
How 5.30.0 went

5.29.0

Added 1
  • Add `actor.select(selector, equalityFn?)` method to derive a `Readable<TSelected>` from an actor's snapshot with `.subscribe()` and `.get()` methods for accessing selected values
Minor Changes
  • #5299 ca8306f Thanks @Uniqen! - Add actor.select(selector, equalityFn?) method to derive a Readable<TSelected> from an actor's snapshot. The returned object has .subscribe() (only emits when the selected value changes, using Object.is by default) and .get() for synchronous access.

    const actor = createActor(machine);
    actor.start();
    
    const count = actor.select((snap) => snap.context.count);
    
    count.get(); // current value
    
    count.subscribe((value) => {
      console.log(value); // only fires when count changes
    });
    
View originalPermalink
How 5.29.0 went

5.28.0

Added 1
  • Added routable states that can be navigated to from anywhere via xstate.route events, with support for guards to conditionally restrict navigation
Fixed 1
  • Export types so setup() declaration emit works
Minor Changes
  • #4184 a741fe7 Thanks @davidkpiano! - Added routable states. States with route: {} and an explicit id can be navigated to from anywhere via a single { type: 'xstate.route', to: '#id' } event.

    const machine = setup({}).createMachine({
      id: 'app',
      initial: 'home',
      states: {
        home: { id: 'home', route: {} },
        dashboard: {
          initial: 'overview',
          states: {
            overview: { id: 'overview', route: {} },
            settings: { id: 'settings', route: {} }
          }
        }
      }
    });
    
    const actor = createActor(machine).start();
    
    // Route directly to deeply nested state from anywhere
    actor.send({ type: 'xstate.route', to: '#settings' });
    

    Routes support guards for conditional navigation:

    settings: {
      id: 'settings',
      route: {
        guard: ({ context }) => context.role === 'admin'
      }
    }
    
Patch Changes
View originalPermalink
How 5.28.0 went

5.27.0

Added 1
  • Add getInitialMicrosteps() and getMicrosteps() functions that return an array of [snapshot, actions] tuples for each microstep in a transition
Minor Changes
  • #5457 287b51e Thanks @davidkpiano! - Add getInitialMicrosteps(…) and getMicrosteps(…) functions that return an array of [snapshot, actions] tuples for each microstep in a transition.

    import { createMachine, getInitialMicrosteps, getMicrosteps } from 'xstate';
    
    const machine = createMachine({
      initial: 'a',
      states: {
        a: {
          entry: () => console.log('enter a'),
          on: {
            NEXT: 'b'
          }
        },
        b: {
          entry: () => console.log('enter b'),
          always: 'c'
        },
        c: {}
      }
    });
    
    // Get microsteps from initial transition
    const initialMicrosteps = getInitialMicrosteps(machine);
    // Returns: [
    //  [snapshotA, [entryActionA]]
    // ]
    
    // Get microsteps from a transition
    const microsteps = getMicrosteps(machine, initialMicrosteps[0][0], {
      type: 'NEXT'
    });
    // Returns: [
    //  [snapshotB, [entryActionB]],
    //  [snapshotC, []]
    // ]
    
    // Each microstep is a tuple of [snapshot, actions]
    for (const [snapshot, actions] of microsteps) {
      console.log('State:', snapshot.value);
      console.log('Actions:', actions.length);
    }
    
View originalPermalink
How 5.27.0 went

5.26.0

Added 1
  • Add getNextTransitions(state) utility to get all transitions available from the current state
Minor Changes
  • #5406 703c3a1 Thanks @davidkpiano! - Add getNextTransitions(state) utility to get all transitions available from current state.

    import { getNextTransitions } from 'xstate';
    
    // ...
    
    const state = actor.getSnapshot();
    const transitions = getNextTransitions(state);
    
    transitions.forEach((t) => {
      console.log(`Event: ${t.eventType}, Source: ${t.source.key}`);
    });
    
View originalPermalink
How 5.26.0 went

5.25.1

Fixed 1
  • Fix systemId cleanup for nested children on stopChild
Patch Changes
View originalPermalink
How 5.25.1 went

5.25.0

Added 1
  • Add partial descriptor support to assertEvent() to match events with types that start with a specified prefix using wildcard notation
Fixed 1
  • Fix a bug in Cordova when iterating an empty Map
Minor Changes
  • #5422 329297b Thanks @davidkpiano! - Add partial descriptor support to assertEvent(…)

    // Matches any event with a type that starts with `FEEDBACK.`
    assertEvent(event, 'FEEDBACK.*');
    
Patch Changes
View originalPermalink
How 5.25.0 went

5.24.0

Added 1
  • Add setup.extend() method to incrementally extend machine setup configurations with additional actions, guards, and delays, enabling composable and reusable machine setups where extended actions, guards, and delays can reference base actions, guards, and delays and support chaining multiple extensions
Minor Changes
  • #5371 b8ec3b1 Thanks @davidkpiano! - Add setup.extend() method to incrementally extend machine setup configurations with additional actions, guards, and delays. This enables composable and reusable machine setups where extended actions, guards, and delays can reference base actions, guards, and delays and support chaining multiple extensions:

    import { setup, not, and } from 'xstate';
    
    const baseSetup = setup({
      guards: {
        isAuthenticated: () => true,
        hasPermission: () => false
      }
    });
    
    const extendedSetup = baseSetup.extend({
      guards: {
        // Type-safe guard references
        isUnauthenticated: not('isAuthenticated'),
        canAccess: and(['isAuthenticated', 'hasPermission'])
      }
    });
    
    // Both base and extended guards are available
    extendedSetup.createMachine({
      on: {
        LOGIN: {
          guard: 'isAuthenticated',
          target: 'authenticated'
        },
        LOGOUT: {
          guard: 'isUnauthenticated',
          target: 'unauthenticated'
        }
      }
    });
    
View originalPermalink
How 5.24.0 went

5.23.0

Added 1
  • Add `system.getAll` that returns a record of running actors within the system by their system id
Minor Changes
  • #5387 53dd7f1 Thanks @farskid! - Adds system.getAll that returns a record of running actors within the system by their system id

    const childMachine = createMachine({});
    const machine = createMachine({
      // ...
      invoke: [
        {
          src: childMachine,
          systemId: 'test'
        }
      ]
    });
    const system = createActor(machine);
    
    system.getAll(); // { test: ActorRefFrom<typeof childMachine> }
    
View originalPermalink
How 5.23.0 went

5.22.1

Changed 1
  • Make actor.systemId public so it can be accessed after actor creation
Fixed 1
  • Remove eventType from required fields in initialTransitionObject
Patch Changes
  • #5379 98f9ddd Thanks @davidkpiano! - Make actor.systemId public:

    const actor = createActor(machine, { systemId: 'test' });
    actor.systemId; // 'test'
    
  • #5380 e7e5e44 Thanks @Nirajkashyap! - fix: remove 'eventType' from required fields in initialTransitionObject

View originalPermalink
How 5.22.1 went

5.22.0

Added 1
  • Add type-bound action helpers to setup(): createAction(fn) for creating type-safe custom actions, and setup-scoped helpers including assign(), sendTo(), raise(), log(), cancel(), stopChild(), enqueueActions(), emit(), and spawnChild() that are fully typed to the setup's context/events/actors/guards/delays/emitted
Minor Changes
  • #5367 76c857e Thanks @davidkpiano! - Add type-bound action helpers to setup():

    • createAction(fn) – create type-safe custom actions
    • setup().assign(...), setup().sendTo(...), setup().raise(...), setup().log(...), setup().cancel(...), setup().stopChild(...), setup().enqueueActions(...), setup().emit(...), setup().spawnChild(...) – setup-scoped helpers that are fully typed to the setup's context/events/actors/guards/delays/emitted.

    These helpers return actions that are bound to the specific setup() they were created from and can be used directly in the machine produced by that setup.

    const machineSetup = setup({
      types: {} as {
        context: {
          count: number;
        };
        events: { type: 'inc'; value: number } | { type: 'TEST' };
        emitted: { type: 'PING' };
      }
    });
    
    // Custom action
    const action = machineSetup.createAction(({ context, event }) => {
      console.log(context.count, event.value);
    });
    
    // Type-bound built-ins (no wrapper needed)
    const increment = machineSetup.assign({
      count: ({ context }) => context.count + 1
    });
    const raiseTest = machineSetup.raise({ type: 'TEST' });
    const ping = machineSetup.emit({ type: 'PING' });
    const batch = machineSetup.enqueueActions(({ enqueue, check }) => {
      if (check(() => true)) {
        enqueue(increment);
      }
    });
    
    const machine = machineSetup.createMachine({
      context: { count: 0 },
      entry: [action, increment, raiseTest, ping, batch]
    });
    
View originalPermalink
How 5.22.0 went

5.21.0

Added 1
  • Added .createStateConfig(…) to the setup API to create state configs that are strongly typed and modular
Minor Changes
  • #5364 15e15b5 Thanks @davidkpiano! - Added .createStateConfig(…) to the setup API. This makes it possible to create state configs that are strongly typed and modular.

    const lightMachineSetup = setup({
      // ...
    });
    
    const green = lightMachineSetup.createStateConfig({
      //...
    });
    
    const yellow = lightMachineSetup.createStateConfig({
      //...
    });
    
    const red = lightMachineSetup.createStateConfig({
      //...
    });
    
    const machine = lightMachineSetup.createMachine({
      initial: 'green',
      states: {
        green,
        yellow,
        red
      }
    });
    
View originalPermalink
How 5.21.0 went

5.20.2

Fixed 1
  • Emit callback errors no longer crash the actor
Patch Changes
  • #5351 71387ff Thanks @davidkpiano! - Fix: Emit callback errors no longer crash the actor

    actor.on('event', () => {
      // Will no longer crash the actor
      throw new Error('oops');
    });
    
View originalPermalink
How 5.20.2 went
View all

Discussion