Skip to main content
Module

x/xstate/CHANGELOG.md

State machines and statecharts for the modern web.
Go to Latest
File

xstate

4.32.1

Patch Changes

  • #3292 16514e466 Thanks @Andarist! - Fixed an issue in the EmittedFrom type helper that could prevent it from inferring the desired type from some services.

4.32.0

Minor Changes

  • #3234 ce376b388 Thanks @Andarist! - Added a StateValueFrom helper that can be used to extract valid state values from a machine. This might specifically be useful with typegen because typegenless state.matches accepts any anyway.

Patch Changes

  • #3215 44c66e74f Thanks @tom-sherman! - Removing the timeout that’s built in to waitFor is now supported by explicitly passing an Infinity value.

    Example usage:

    import { waitFor } from 'xstate/lib/waitFor';
    
    // This will
    const loggedInState = await waitFor(
      loginService,
      state => state.hasTag('loggedIn'),
      { timeout: Infinity }
    );

    This fixes a bug that causes waitFor to reject with an error immediately due to the behaviour of setTimeout.

  • #3230 780458c92 Thanks @Andarist! - Fixed an issue with typegen types not being able to provide events that had a union of strings as their type (such as { type: 'INC' | 'DEC'; value: number; }).
  • #3252 a94dfd467 Thanks @Andarist! - Fixed an issue with EventFrom not being able to extract events that had a union of strings as their type (such as { type: 'INC' | 'DEC'; value: number; }).
  • #3090 c4f73ca13 Thanks @Andarist! - Fixed an issue with action objects not receiving correct event types when used in the second argument to the createMachine.
  • #3228 fe5f0e6c9 Thanks @Andarist! - Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred.
  • #3252 a94dfd467 Thanks @Andarist! - Fixed an issue with default TEvent ({ type: string }) not being correctly provided to inline transition actions.

4.31.0

Minor Changes

  • #3190 fbf5ca0ad Thanks @davidkpiano! - The waitFor(...) helper function, which asynchronously waits for an actor’s emitted value to satisfy a predicate before a timeout, is now available.

    Example usage:

    import { waitFor } from 'xstate/lib/waitFor';
    
    // ...
    const loginService = interpret(loginMachine).start();
    
    const loggedInState = await waitFor(loginService, state =>
      state.hasTag('loggedIn')
    );
    
    loggedInState.hasTag('loggedIn'); // true
  • #3200 56c0a36 Thanks @Andarist! - Subscribing to a stopped interpreter will now always immediately emit its state and call a completion callback.

Patch Changes

  • #3166 be4c5c74d Thanks @Andarist! - Fixed an issue with state.tags not having correct values when resolving micro transitions (taken in response to raised events). This was creating issues when checking tags in guards.
  • #3171 14f8b4785 Thanks @Andarist! - Fixed an issue with onDone on parallel states not being “called” correctly when a parallel state had a history state defined directly on it.
  • #2939 360e85462 Thanks @Andarist! - Fixed issues with not disposing some cached internal values when stopping interpreters, which could have led to issues when starting such an interpreter again.
  • #3153 b36ef9dda Thanks @Andarist! - Made type displays (like in the IDE tooltips etc) more readable by using a type interface for the internal ResolveTypegenMeta type.

4.30.6

Patch Changes

  • #3131 d9a0bcfc9 Thanks @Andarist! - Fixed an issue with event type being inferred from too many places within createMachine call and possibly ending up as any/AnyEventObject for the entire machine.
  • #3133 4feef9d47 Thanks @fw6! - Fixed compatibility with esoteric Mini Program environment where global object was available but global.console wasn’t.
  • #3140 502ffe91a Thanks @Andarist! - Fixed an issue with interpreters started using a persisted state not being “resolved” in full. This could cause some things, such as after transitions, not being executed correctly after starting an interpreter like this.
  • #3147 155539c85 Thanks @Andarist! - Fixed a TS inference issue causing some functions to infer the constraint type for the event type even though a StateMachine passed to the function was parametrized with a concrete type for the event. More information can be found here.
  • #3146 4cf89b5f9 Thanks @Andarist! - Fixed compatibility of Interpreter with older versions of TypeScript. This ensures that our interpreters can correctly be consumed by functions expecting ActorRef interface (like for example useSelector).
  • #3139 7b45fda9e Thanks @Andarist! - InterpreterFrom and ActorRefFrom types used on machines with typegen data should now correctly return types with final/resolved typegen data. The “final” type here means a type that already encodes the information that all required implementations have been provided. Before this change this wouldn’t typecheck correctly:

    const machine = createMachine({
      // this encodes that we still expect `myAction` to be provided
      tsTypes: {} as Typegen0
    });
    const service: InterpreterFrom<typeof machine> = machine.withConfig({
      actions: {
        myAction: () => {}
      }
    });
  • #3097 c881c8ca9 Thanks @davidkpiano! - State that is persisted and restored from machine.resolveState(state) will now have the correct state.machine value, so that state.can(...) and other methods will work as expected. See #3096 for more details.

4.30.5

Patch Changes

  • #3118 28e353081 Thanks @Andarist! - Fixed a bundling issue that prevented the keys() export to be preserved in the previous release.

4.30.4

Patch Changes

  • #3113 144131bed Thanks @davidkpiano! - The keys() utility function export, which was removed in #3089, is now added back, as older versions of XState libraries may depend on it still. See #3106 for more details.

4.30.3

Patch Changes

  • #3088 9f02271a3 Thanks @Andarist! - Added some internal @ts-ignore comments to fix consuming projects that do not use skipLibCheck.
  • #3082 8d3f2cfea Thanks @Andarist! - Fixed an issue with context type being inferred from too many places within createMachine call and possibly ending up as any for the entire machine.
  • #3027 97ad964bd Thanks @hedgepigdaniel! - Fixed an issue with not being able to call createMachine in a generic context when the type for the context was generic and not concrete.
  • #3084 50c271dc1 Thanks @Andarist! - Fixed an issue with context type defined using schema.context being sometimes widened based on config.context. If both are given the schema.context should always take precedence and should represent the complete type of the context.
  • #3089 862697e29 Thanks @Andarist! - Fixed compatibility with Skypack by exporting some shared utilities from root entry of XState and consuming them directly in other packages (this avoids accessing those things using deep imports and thus it avoids creating those compatibility problems).
  • #3087 ae9579497 Thanks @Andarist! - Fixed an issue with ActorRefFrom not resolving the typegen metadata from machine types given to it. This could sometimes result in types assignability problems, especially when using machine factories and spawn.

4.30.2

Patch Changes

  • #3063 c826559b4 Thanks @Andarist! - Fixed a type compatibility with Svelte’s readables. It should be possible again to use XState interpreters directly as readables at the type-level.
  • #3051 04091f29c Thanks @Andarist! - Fixed type compatibility with functions accepting machines that were created before typegen was a thing in XState. This should make it possible to use the latest version of XState with @xstate/vue, @xstate/react@^1 and some community packages.

    Note that this change doesn’t make those functions to accept machines that have typegen information on them. For that the signatures of those functions would have to be adjusted.

  • #3077 2c76ecac5 Thanks @Andarist! - Fixed an issue with nested state.matches calls when the typegen was involved. The state ended up being never and thus not usable.

4.30.1

Patch Changes

  • #3040 18dc2b3e2 Thanks @davidkpiano! - The AnyState and AnyStateMachine types are now available, which can be used to express any state and state machine, respectively:

    import type { AnyState, AnyStateMachine } from 'xstate';
    
    // A function that takes in any state machine
    function visualizeMachine(machine: AnyStateMachine) {
      // (exercise left to reader)
    }
    
    function logState(state: AnyState) {
      // ...
    }
  • #3042 e53396f08 Thanks @suerta-git! - Added the AnyStateConfig type, which represents any StateConfig<...>:

    import type { AnyStateConfig } from 'xstate';
    import { State } from 'xstate';
    
    // Retrieving the state config from localStorage
    const stateConfig: AnyStateConfig = JSON.parse(
      localStorage.getItem('app-state')
    );
    
    // Use State.create() to restore state from config object with correct type
    const previousState = State.create(stateConfig);

4.30.0

Minor Changes

  • #2965 8b8f719c3 Thanks @satyasinha! - All actions are now available in the actions variable when importing: import { actions } from 'xstate'
  • #2892 02de3d44f Thanks @davidkpiano! - Persisted state can now be easily restored to a state compatible with the machine without converting it to a State instance first:

    // Persisting a state
    someService.subscribe(state => {
      localStorage.setItem('some-state', JSON.stringify(state));
    });
    
    // Restoring a state
    const stateJson = localStorage.getItem('some-state');
    
    // No need to convert `stateJson` object to a state!
    const someService = interpret(someMachine).start(stateJson);

Patch Changes

  • #3012 ab431dcb8 Thanks @Andarist! - Fixed an issue with a reference to @types/node being inserted into XState’s compiled output. This could cause unexpected issues in projects expecting APIs like setTimeout to be typed with browser compatibility in mind.
  • #3023 642e9f5b8 Thanks @Andarist! - Fixed an issue with states created using machine.getInitialState not being “resolved” in full. This could cause some things, such as after transitions, not being executed correctly after starting an interpreter using such state.
  • #2982 a39145580 Thanks @Andarist! - Marked all phantom properties on the StateMachine type as deprecated. This deprioritized them in IDEs so they don’t popup as first suggestions during property access.
  • #2981 edf60d67b Thanks @Andarist! - Moved an internal @ts-ignore to a JSDoc-style comment to fix consuming projects that do not use skipLibCheck. Regular inline and block comments are not preserved in the TypeScript’s emit.

4.29.0

Minor Changes

  • #2674 1cd26811c Thanks @Andarist! - Using config.schema becomes the preferred way of “declaring” TypeScript generics with this release:

    createMachine({
        schema: {
            context: {} as { count: number },
            events: {} as { type: 'INC' } | { type: 'DEC' }
        }
    })

    This allows us to leverage the inference algorithm better and unlocks some exciting possibilities for using XState in a more type-strict manner.

  • #2674 1cd26811c Thanks @Andarist, @mattpocock! - Added the ability to tighten TS declarations of machine with generated metadata. This opens several exciting doors to being able to use typegen seamlessly with XState to provide an amazing typing experience.

    With the VS Code extension, you can specify a new attribute called tsTypes: {} in your machine definition:

    const machine = createMachine({
      tsTypes: {}
    });

    The extension will automatically add a type assertion to this property, which allows for type-safe access to a lot of XState’s API’s.

    ⚠️ This feature is in beta. Actions/services/guards/delays might currently get incorrectly annotated if they are called “in response” to always transitions or raised events. We are working on fixing this, both in XState and in the typegen.

Patch Changes

  • #2962 32520650b Thanks @mattpocock! - Added t(), which can be used to provide types for schema attributes in machine configs:

    import { t, createMachine } from 'xstate';
    
    const machine = createMachine({
      schema: {
        context: t<{ value: number }>(),
        events: t<{ type: 'EVENT_1' } | { type: 'EVENT_2' }>()
      }
    });
  • #2957 8550ddda7 Thanks @davidkpiano! - The repository links have been updated from github.com/davidkpiano to github.com/statelyai.

4.28.1

Patch Changes

  • #2943 e9f3f07a1 Thanks @Andarist! - Fixed an infinite loop when initially spawned actor (in an initial context) responded synchronously to its parent.
  • #2953 90fa97008 Thanks @Andarist! - Bring back the global type declaration for the Symbol.observable to fix consuming projects that do not use skipLibCheck.
  • #2903 b6dde9075 Thanks @Andarist! - Fixed an issue with exit actions being called in random order when stopping a machine. They should always be called in the reversed document order (the ones defined on children should be called before the ones defined on ancestors and the ones defined on states appearing later in the code should be called before the ones defined on their sibling states).

4.28.0

Minor Changes

  • #2835 029f7b75a Thanks @woutermont! - Added interop observable symbols to ActorRef so that actor refs are compatible with libraries like RxJS.

Patch Changes

  • #2864 4252ee212 Thanks @davidkpiano! - Generated IDs for invocations that do not provide an id are now based on the state ID to avoid collisions:

    createMachine({
      id: 'test',
      initial: 'p',
      states: {
        p: {
          type: 'parallel',
          states: {
            // Before this change, both invoke IDs would be 'someSource',
            // which is incorrect.
            a: {
              invoke: {
                src: 'someSource'
                // generated invoke ID: 'test.p.a:invocation[0]'
              }
            },
            b: {
              invoke: {
                src: 'someSource'
                // generated invoke ID: 'test.p.b:invocation[0]'
              }
            }
          }
        }
      }
    });
  • #2925 239b4666a Thanks @devanfarrell! - The sendTo(actorRef, event) action creator introduced in 4.27.0, which was not accessible from the package exports, can now be used just like other actions:

    import { actions } from 'xstate';
    
    const { sendTo } = actions;

4.27.0

Minor Changes

  • #2800 759a90155 Thanks @davidkpiano! - The sendTo(actorRef, event) action creator has been introduced. It allows you to specify the recipient actor ref of an event first, so that the event can be strongly typed against the events allowed to be received by the actor ref:

    // ...
    entry: sendTo(
      (ctx) => ctx.someActorRef,
      { type: 'EVENT_FOR_ACTOR' }
    ),
    // ...

Patch Changes

  • #2804 f3caecf5a Thanks @davidkpiano! - The state.can(...) method no longer unnecessarily executes assign() actions and instead determines if a given event will change the state by reading transition data before evaluating actions.
  • #2856 49c2e9094 Thanks @Andarist! - Fixed an issue with stopped children sometimes starting their own child actors. This could happen when the child was stopped synchronously (for example by its parent) when transitioning to an invoking state.
  • #2895 df5ffce14 Thanks @Andarist! - Fixed an issue with some exit handlers being executed more than once when stopping a machine.

4.26.1

Patch Changes

4.26.0

Minor Changes

  • #2672 8e1d05d Thanks @davidkpiano! - The description property is a new top-level property for state nodes and transitions, that lets you provide text descriptions:

    const machine = createMachine({
      // ...
      states: {
        active: {
          // ...
          description: 'The task is in progress',
          on: {
            DEACTIVATE: {
              // ...
              description: 'Deactivates the task'
            }
          }
        }
      }
    });

    Future Stately tooling will use the description to render automatically generated documentation, type hints, and enhancements to visual tools.

  • #2743 e268bf34a Thanks @janovekj! - Add optional type parameter to narrow type returned by EventFrom. You can use it like this:

    type UpdateNameEvent = EventFrom<typeof userModel>;

Patch Changes

  • #2738 942fd90e0 Thanks @michelsciortino! - The tags property was missing from state’s definitions. This is used when converting a state to a JSON string. Since this is how we serialize states within @xstate/inspect this has caused inspected machines to miss the tags information.
  • #2740 707cb981f Thanks @Andarist! - Fixed an issue with tags being missed on a service state after starting that service using a state value, like this:

    const service = interpret(machine).start('active');
    service.state.hasTag('foo'); // this should now return a correct result
  • #2691 a72806035 Thanks @davidkpiano! - Meta data can now be specified for invoke configs in the invoke.meta property:

    const machine = createMachine({
      // ...
      invoke: {
        src: (ctx, e) => findUser(ctx.userId),
        meta: {
          summary: 'Finds user',
          updatedAt: '2021-09-...',
          version: '4.12.2'
          // other descriptive meta properties
        }
      }
    });

4.25.0

Minor Changes

  • #2657 72155c1b7 Thanks @mattpocock! - Removed the ability to pass a model as a generic to createMachine, in favour of model.createMachine. This lets us cut an overload from the definition of createMachine, meaning errors become more targeted and less cryptic.

    This means that this approach is no longer supported:

    const model = createModel({});
    
    const machine = createMachine<typeof model>();

    If you’re using this approach, you should use model.createMachine instead:

    const model = createModel({});
    
    const machine = model.createMachine();

Patch Changes

  • #2659 7bfeb930d Thanks @Andarist! - Fixed a regression in the inline actions type inference in models without explicit action creators.

    const model = createModel(
      { foo: 100 },
      {
        events: {
          BAR: () => ({})
        }
      }
    );
    
    model.createMachine({
      // `ctx` was of type `any`
      entry: ctx => {},
      exit: assign({
        // `ctx` was of type `unknown`
        foo: ctx => 42
      })
    });

4.24.1

Patch Changes

  • #2649 ad611007a Thanks @Andarist! - Fixed an issue with functions used as inline actions not always receiving the correct arguments when used with preserveActionOrder.

4.24.0

Minor Changes

  • #2546 a4cfce18c Thanks @davidkpiano! - You can now know if an event will cause a state change by using the new state.can(event) method, which will return true if an interpreted machine will “change” the state when sent the event, or false otherwise:

    const machine = createMachine({
      initial: 'inactive',
      states: {
        inactive: {
          on: {
            TOGGLE: 'active'
          }
        },
        active: {
          on: {
            DO_SOMETHING: { actions: ['something'] }
          }
        }
      }
    });
    
    const state = machine.initialState;
    
    state.can('TOGGLE'); // true
    state.can('DO_SOMETHING'); // false
    
    // Also takes in full event objects:
    state.can({
      type: 'DO_SOMETHING',
      data: 42
    }); // false

    A state is considered “changed” if any of the following are true:

    • its state.value changes
    • there are new state.actions to be executed
    • its state.context changes

    See state.changed (documentation) for more details.

Patch Changes

  • #2632 f8cf5dfe0 Thanks @davidkpiano! - A regression was fixed where actions were being typed as never if events were specified in createModel(...) but not actions:

    const model = createModel(
      {},
      {
        events: {}
      }
    );
    
    model.createMachine({
      // These actions will cause TS to not compile
      entry: 'someAction',
      exit: { type: 'someObjectAction' }
    });

4.23.4

Patch Changes

  • #2606 01e5d7984 Thanks @davidkpiano! - The following utility types were previously returning never in some unexpected cases, and are now working as expected:

    • ContextFrom<T>
    • EventFrom<T>
    • EmittedFrom<T>

4.23.3

Patch Changes

  • #2587 5aaa8445c Thanks @Andarist! - Allow for guards to be always resolved from the implementations object. This allows a guard implementation to be updated in the running service by @xstate/react.

4.23.2

Patch Changes

  • 6c3f15c9 #2551 Thanks @mattpocock! - Widened the *From utility types to allow extracting from factory functions.

    This allows for:

    const makeMachine = () => createMachine({});
    
    type Interpreter = InterpreterFrom<typeof makeMachine>;
    type Actor = ActorRefFrom<typeof makeMachine>;
    type Context = ContextFrom<typeof makeMachine>;
    type Event = EventsFrom<typeof makeMachine>;

    This also works for models, behaviours, and other actor types.

    The previous method for doing this was a good bit more verbose:

    const makeMachine = () => createMachine({});
    
    type Interpreter = InterpreterFrom<ReturnType<typeof machine>>;
  • 413a4578 #2491 Thanks @davidkpiano! - The custom .toString() method on action objects is now removed which improves performance in larger applications (see #2488 for more context).
  • 5e1223cd #2422 Thanks @davidkpiano! - The context property has been removed from StateNodeConfig, as it has never been allowed, nor has it ever done anything. The previous typing was unsafe and allowed context to be specified on nested state nodes:

    createMachine({
      context: {
        /* ... */
      }, // ✅ This is allowed
      initial: 'inner',
      states: {
        inner: {
          context: {
            /* ... */
          } // ❌ This will no longer compile
        }
      }
    });
  • 5b70c2ff #2508 Thanks @davidkpiano! - A race condition occurred when a child service is immediately stopped and the parent service tried to remove it from its undefined state (during its own initialization). This has been fixed, and the race condition no longer occurs. See this issue for details.
  • 5a9500d1 #2522 Thanks @farskid, @Andarist! - Adjusted TS type definitions of the withContext and withConfig methods so that they accept “lazy context” now.

    Example:

    const copy = machine.withContext(() => ({
      ref: spawn(() => {})
    }));
  • 84f9fcae #2540 Thanks @Andarist! - Fixed an issue with state.hasTag('someTag') crashing when the state was rehydrated.
  • c17dd376 #2496 Thanks @VanTanev! - Add utility type EmittedFrom<T> that extracts Emitted type from any type which can emit data

4.23.1

Patch Changes

  • 141c91cf #2436 Thanks @Andarist! - Fixed an issue where, when using model.createMachine, state’s context was incorrectly inferred as any after refinement with .matches(...), e.g.

    // `state.context` became `any` erroneously
    if (state.matches('inactive')) {
      console.log(state.context.count);
    }

4.23.0

Minor Changes

  • 7dc7ceb8 #2379 Thanks @davidkpiano! - There is a new .preserveActionOrder (default: false) setting in the machine configuration that preserves the order of actions when set to true. Normally, actions are executed in order except for assign(...) actions, which are prioritized and executed first. When .preserveActionOrder is set to true, assign(...) actions will not be prioritized, and will instead run in order. As a result, actions will capture the intermediate context values instead of the resulting context value from all assign(...) actions.

    // With `.preserveActionOrder: true`
    const machine = createMachine({
      context: { count: 0 },
      entry: [
        ctx => console.log(ctx.count), // 0
        assign({ count: ctx => ctx.count + 1 }),
        ctx => console.log(ctx.count), // 1
        assign({ count: ctx => ctx.count + 1 }),
        ctx => console.log(ctx.count) // 2
      ],
      preserveActionOrder: true
    });
    
    // With `.preserveActionOrder: false` (default)
    const machine = createMachine({
      context: { count: 0 },
      entry: [
        ctx => console.log(ctx.count), // 2
        assign({ count: ctx => ctx.count + 1 }),
        ctx => console.log(ctx.count), // 2
        assign({ count: ctx => ctx.count + 1 }),
        ctx => console.log(ctx.count) // 2
      ]
      // preserveActionOrder: false
    });

Patch Changes

  • 4e305372 #2361 Thanks @woutermont! - Add type for Symbol.observable to the Interpreter to improve the compatibility with RxJS.
  • 1def6cf6 #2374 Thanks @davidkpiano! - Existing actors can now be identified in spawn(...) calls by providing an id. This allows them to be referenced by string:

    const machine = createMachine({
      context: () => ({
        someRef: spawn(someExistingRef, 'something')
      }),
      on: {
        SOME_EVENT: {
          actions: send('AN_EVENT', { to: 'something' })
        }
      }
    });
  • da6861e3 #2391 Thanks @davidkpiano! - There are two new helper types for extracting context and event types:

    • ContextFrom<T> which extracts the context from any type that uses context
    • EventFrom<T> which extracts the event type (which extends EventObject) from any type which uses events

4.22.0

Minor Changes

  • 1b32aa0d #2356 Thanks @davidkpiano! - The model created from createModel(...) now provides a .createMachine(...) method that does not require passing any generic type parameters:

    const model = createModel(/* ... */);
    
    -const machine = createMachine<typeof model>(/* ... */);
    +const machine = model.createMachine(/* ... */);
  • 432b60f7 #2280 Thanks @davidkpiano! - Actors can now be invoked/spawned from reducers using the fromReducer(...) behavior creator:

    import { fromReducer } from 'xstate/lib/behaviors';
    
    type CountEvent = { type: 'INC' } | { type: 'DEC' };
    
    const countReducer = (count: number, event: CountEvent): number => {
      if (event.type === 'INC') {
        return count + 1;
      } else if (event.type === 'DEC') {
        return count - 1;
      }
    
      return count;
    };
    
    const countMachine = createMachine({
      invoke: {
        id: 'count',
        src: () => fromReducer(countReducer, 0)
      },
      on: {
        INC: {
          actions: forwardTo('count')
        },
        DEC: {
          actions: forwardTo('count')
        }
      }
    });
  • f9bcea2c #2366 Thanks @davidkpiano! - Actors can now be spawned directly in the initial machine.context using lazy initialization, avoiding the need for intermediate states and unsafe typings for immediately spawned actors:

    const machine = createMachine<{ ref: ActorRef<SomeEvent> }>({
      context: () => ({
        ref: spawn(anotherMachine, 'some-id') // spawn immediately!
      })
      // ...
    });

4.20.2

Patch Changes

  • 1ef29e83 #2343 Thanks @davidkpiano! - Eventless (“always”) transitions will no longer be ignored if an event is sent to a machine in a state that does not have any enabled transitions for that event.

4.20.1

Patch Changes

  • 99bc5fb9 #2275 Thanks @davidkpiano! - The SpawnedActorRef TypeScript interface has been deprecated in favor of a unified ActorRef interface, which contains the following:

    interface ActorRef<TEvent extends EventObject, TEmitted = any>
      extends Subscribable<TEmitted> {
      send: (event: TEvent) => void;
      id: string;
      subscribe(observer: Observer<T>): Subscription;
      subscribe(
        next: (value: T) => void,
        error?: (error: any) => void,
        complete?: () => void
      ): Subscription;
      getSnapshot: () => TEmitted | undefined;
    }

    For simpler actor-ref-like objects, the BaseActorRef<TEvent> interface has been introduced.

    interface BaseActorRef<TEvent extends EventObject> {
      send: (event: TEvent) => void;
    }
  • 38e6a5e9 #2334 Thanks @davidkpiano! - When using a model type in createMachine<typeof someModel>(...), TypeScript will no longer compile machines that are missing the context property in the machine configuration:

    const machine = createMachine<typeof someModel>({
      // missing context - will give a TS error!
      // context: someModel.initialContext,
      initial: 'somewhere',
      states: {
        somewhere: {}
      }
    });
  • 5f790ba5 #2320 Thanks @davidkpiano! - The typing for InvokeCallback have been improved for better event constraints when using the sendBack parameter of invoked callbacks:

    invoke: () => (sendBack, receive) => {
      // Will now be constrained to events that the parent machine can receive
      sendBack({ type: 'SOME_EVENT' });
    };
  • 2de3ec3e #2272 Thanks @davidkpiano! - The state.meta value is now calculated directly from state.configuration. This is most useful when starting a service from a persisted state:

      const machine = createMachine({
        id: 'test',
        initial: 'first',
        states: {
          first: {
            meta: {
              name: 'first state'
            }
          },
          second: {
            meta: {
              name: 'second state'
            }
          }
        }
      });
    
      const service = interpret(machine);
    
      service.start('second'); // `meta` will be computed
    
      // the state will have
      // meta: {
      //   'test.second': {
      //     name: 'second state'
      //   }
      // }
    });

4.20.0

Minor Changes

  • 28059b9f #2197 Thanks @davidkpiano! - All spawned and invoked actors now have a .getSnapshot() method, which allows you to retrieve the latest value emitted from that actor. That value may be undefined if no value has been emitted yet.

    const machine = createMachine({
      context: {
        promiseRef: null
      },
      initial: 'pending',
      states: {
        pending: {
          entry: assign({
            promiseRef: () => spawn(fetch(/* ... */), 'some-promise')
          })
        }
      }
    });
    
    const service = interpret(machine)
      .onTransition(state => {
        // Read promise value synchronously
        const resolvedValue = state.context.promiseRef?.getSnapshot();
        // => undefined (if promise not resolved yet)
        // => { ... } (resolved data)
      })
      .start();
    
    // ...

Patch Changes

  • 4ef03465 #2240 Thanks @VanTanev! - Preserve StateMachine type when .withConfig() and .withContext() modifiers are used on a machine.

4.19.2

Patch Changes

  • 18789aa9 #2107 Thanks @woutermont! - This update restricts invoked Subscribables to EventObjects, so that type inference can be done on which Subscribables are allowed to be invoked. Existing MachineConfigs that invoke Subscribable<any>s that are not Subscribable<EventObject>s should be updated accordingly.
  • 38dcec1d #2149 Thanks @davidkpiano! - Invocations and entry actions for combinatorial machines (machines with only a single root state) now behave predictably and will not re-execute upon targetless transitions.

4.19.1

Patch Changes

  • 64ab1150 #2173 Thanks @Andarist! - Fixed an issue with tags not being set correctly after sending an event to a machine that didn’t result in selecting any transitions.

4.19.0

Minor Changes

  • 4f2f626d #2143 Thanks @davidkpiano! - Tags can now be added to state node configs under the .tags property:

    const machine = createMachine({
      initial: 'green',
      states: {
        green: {
          tags: 'go' // single tag
        },
        yellow: {
          tags: 'go'
        },
        red: {
          tags: ['stop', 'other'] // multiple tags
        }
      }
    });

    You can query whether a state has a tag via state.hasTag(tag):

    const canGo = state.hasTag('go');
    // => `true` if in 'green' or 'red' state

Patch Changes

  • a61d01ce #2125 Thanks @VanTanev! - In callback invokes, the types of callback and onReceive are properly scoped to the machine TEvent.

4.18.0

Minor Changes

  • d0939ec6 #2046 Thanks @SimeonC! - Allow machines to communicate with the inspector even in production builds.
  • e37fffef #2079 Thanks @davidkpiano! - There is now support for “combinatorial machines” (state machines that only have one state):

    const testMachine = createMachine({
      context: { value: 42 },
      on: {
        INC: {
          actions: assign({ value: ctx => ctx.value + 1 })
        }
      }
    });

    These machines omit the initial and state properties, as the entire machine is treated as a single state.

Patch Changes

  • 6a9247d4 #2102 Thanks @VanTanev! - Provide a convenience type for getting the Interpreter type based on the StateMachine type by transferring all generic parameters onto it. It can be used like this: InterpreterFrom<typeof machine>

4.17.1

Patch Changes

  • 33302814 #2041 Thanks @Andarist! - Fixed an issue with creatorless models not being correctly matched by createMachine’s overload responsible for using model-induced types.

4.17.0

Minor Changes

  • 7763db8d #1977 Thanks @davidkpiano! - The schema property has been introduced to the machine config passed into createMachine(machineConfig), which allows you to provide metadata for the following:

    • Context
    • Events
    • Actions
    • Guards
    • Services

    This metadata can be accessed as-is from machine.schema:

    const machine = createMachine({
      schema: {
        // Example in JSON Schema (anything can be used)
        context: {
          type: 'object',
          properties: {
            foo: { type: 'string' },
            bar: { type: 'number' },
            baz: {
              type: 'object',
              properties: {
                one: { type: 'string' }
              }
            }
          }
        },
        events: {
          FOO: { type: 'object' },
          BAR: { type: 'object' }
        }
      }
      // ...
    });

    Additionally, the new createSchema() identity function allows any schema “metadata” to be represented by a specific type, which makes type inference easier without having to specify generic types:

    import { createSchema, createMachine } from 'xstate';
    
    // Both `context` and `events` are inferred in the rest of the machine!
    const machine = createMachine({
      schema: {
        context: createSchema<{ count: number }>(),
        // No arguments necessary
        events: createSchema<{ type: 'FOO' } | { type: 'BAR' }>()
      }
      // ...
    });
  • 5febfe83 #1955 Thanks @davidkpiano! - Event creators can now be modeled inside of the 2nd argument of createModel(), and types for both context and events will be inferred properly in createMachine() when given the typeof model as the first generic parameter.

    import { createModel } from 'xstate/lib/model';
    
    const userModel = createModel(
      // initial context
      {
        name: 'David',
        age: 30
      },
      // creators (just events for now)
      {
        events: {
          updateName: (value: string) => ({ value }),
          updateAge: (value: number) => ({ value }),
          anotherEvent: () => ({}) // no payload
        }
      }
    );
    
    const machine = createMachine<typeof userModel>({
      context: userModel.initialContext,
      initial: 'active',
      states: {
        active: {
          on: {
            updateName: {
              /* ... */
            },
            updateAge: {
              /* ... */
            }
          }
        }
      }
    });
    
    const nextState = machine.transition(
      undefined,
      userModel.events.updateName('David')
    );

4.16.2

Patch Changes

4.16.1

Patch Changes

  • af6b7c70 #1865 Thanks @Andarist! - Improved .matches(value) inference for typestates containing union types as values.

4.16.0

Minor Changes

  • d2e328f8 #1439 Thanks @davidkpiano! - An opt-in createModel() helper has been introduced to make it easier to work with typed context and events.

    • createModel(initialContext) creates a model object
    • model.initialContext returns the initialContext
    • model.assign(assigner, event?) creates an assign action that is properly scoped to the event in TypeScript

    See https://github.com/statelyai/xstate/pull/1439 for more details.

    import { createMachine } from 'xstate';
    import { createModel } from 'xstate/lib/model'; // opt-in, not part of main build
    
    interface UserContext {
      name: string;
      age: number;
    }
    
    type UserEvents =
      | { type: 'updateName'; value: string }
      | { type: 'updateAge'; value: number }
    
    const userModel = createModel<UserContext, UserEvents>({
      name: 'David',
      age: 30
    });
    
    const assignName = userModel.assign({
      name: (_, e) => e.value // correctly typed to `string`
    }, 'updateName'); // restrict to 'updateName' event
    
    const machine = createMachine<UserContext, UserEvents>({
      context: userModel.context,
      initial: 'active',
      states: {
        active: {
          on: {
            updateName: {
              actions: assignName
            }
          }
        }
      }
    });

4.15.4

Patch Changes

  • 0cb8df9b #1816 Thanks @Andarist! - machine.resolveState(state) calls should resolve to the correct value of .done property now.

4.15.3

Patch Changes

  • 63ba888e #1770 Thanks @davidkpiano! - Instead of referencing window directly, XState now internally calls a getGlobal() function that will resolve to the proper globalThis value in all environments. This affects the dev tools code only.

4.15.2

Patch Changes

  • 497c543d #1766 Thanks @Andarist! - Fixed an issue with events received from callback actors not having the appropriate _event.origin set.

4.15.1

Patch Changes

  • 8a8cfa32 #1704 Thanks @blimmer! - The default clock methods (setTimeout and clearTimeout) are now invoked properly with the global context preserved for those invocations which matter for some JS environments. More details can be found in the corresponding issue: #1703.

4.15.0

Minor Changes

  • 6596d0ba #1622 Thanks @davidkpiano! - Spawned/invoked actors and interpreters are now typed as extending ActorRef (e.g., SpawnedActorRef) rather than Actor or Interpreter. This unification of types should make it more straightforward to provide actor types:

    import {
    - Actor
    + ActorRef
    } from 'xstate';
    
    // ...
    
    interface SomeContext {
    - server?: Actor;
    + server?: ActorRef<ServerEvent>;
    }

    It’s also easier to specify the type of a spawned/invoked machine with ActorRefFrom:

    import {
      createMachine,
    - Actor
    + ActorRefFrom
    } from 'xstate';
    
    const serverMachine = createMachine<ServerContext, ServerEvent>({
      // ...
    });
    
    interface SomeContext {
    - server?: Actor; // difficult to type
    + server?: ActorRefFrom<typeof serverMachine>;
    }

Patch Changes

  • 75a91b07 #1692 Thanks @Andarist! - Fixed an issue with history state entering a wrong state if the most recent visit in its parent has been caused by a transient transition.

4.14.1

Patch Changes

  • 02c76350 #1656 Thanks @Andarist! - Exit actions will now be properly called when a service gets canceled by calling its stop method.

4.14.0

Minor Changes

  • 119db8fb #1577 Thanks @davidkpiano! - Expressions can now be used in the stop() action creator:

    // ...
    actions: stop(context => context.someActor);

Patch Changes

  • 8c78e120 #1570 Thanks @davidkpiano! - The return type of spawn(machine) will now be Actor<State<TContext, TEvent>, TEvent>, which is a supertype of Interpreter<...>.
  • 602687c2 #1566 Thanks @davidkpiano! - Exit actions will now be properly called when an invoked machine reaches its final state. See #1109 for more details.
  • 6e44d02a #1553 Thanks @davidkpiano! - The state.children property now properly shows all spawned and invoked actors. See #795 for more details.
  • 72b0880e #1504 Thanks @Andarist! - Added status property on the Interpreter - this can be used to differentiate not started, running and stopped interpreters. This property is best compared to values on the new InterpreterStatus export.

4.13.0

Minor Changes

  • f51614df #1409 Thanks @jirutka! - Fix type ExtractStateValue so that it generates a type actually describing a State.value

Patch Changes

  • b1684ead #1402 Thanks @Andarist! - Improved TypeScript type-checking performance a little bit by using distributive conditional type within TransitionsConfigArray declarations instead of a mapped type. Kudos to @amcasey, some discussion around this can be found here
  • ad3026d4 #1407 Thanks @tomenden! - Fixed an issue with not being able to run XState in Web Workers due to assuming that window or global object is available in the executing environment, but none of those are actually available in the Web Workers context.
  • 4e949ec8 #1401 Thanks @Andarist! - Fixed an issue with spawned actors being spawned multiple times when they got spawned in an initial state of a child machine that is invoked in the initial state of a parent machine.

    Illustrating example for curious readers.
    const child = createMachine({
      initial: 'bar',
      context: {},
      states: {
        bar: {
          entry: assign({
            promise: () => {
              return spawn(() => Promise.resolve('answer'));
            }
          })
        }
      }
    });
    
    const parent = createMachine({
      initial: 'foo',
      states: {
        foo: {
          invoke: {
            src: child,
            onDone: 'end'
          }
        },
        end: { type: 'final' }
      }
    });
    
    interpret(parent).start();

4.12.0

Minor Changes

  • b72e29dd #1354 Thanks @davidkpiano! - The Action type was simplified, and as a result, you should see better TypeScript performance.
  • 4dbabfe7 #1320 Thanks @davidkpiano! - The invoke.src property now accepts an object that describes the invoke source with its type and other related metadata. This can be read from the services option in the meta.src argument:

    const machine = createMachine(
      {
        initial: 'searching',
        states: {
          searching: {
            invoke: {
              src: {
                type: 'search',
                endpoint: 'example.com'
              }
              // ...
            }
            // ...
          }
        }
      },
      {
        services: {
          search: (context, event, { src }) => {
            console.log(src);
            // => { endpoint: 'example.com' }
          }
        }
      }
    );

    Specifying a string for invoke.src will continue to work the same; e.g., if src: 'search' was specified, this would be the same as src: { type: 'search' }.

  • 8662e543 #1317 Thanks @Andarist! - All TTypestate type parameters default to { value: any; context: TContext } now and the parametrized type is passed correctly between various types which results in more accurate types involving typestates.

Patch Changes

  • 3ab3f25e #1285 Thanks @Andarist! - Fixed an issue with initial state of invoked machines being read without custom data passed to them which could lead to a crash when evaluating transient transitions for the initial state.
  • a7da1451 #1290 Thanks @davidkpiano! - The “Attempted to spawn an Actor […] outside of a service. This will have no effect.” warnings are now silenced for “lazily spawned” actors, which are actors that aren’t immediately active until the function that creates them are called:

    // ⚠️ "active" actor - will warn
    spawn(somePromise);
    
    // 🕐 "lazy" actor - won't warn
    spawn(() => somePromise);
    
    // 🕐 machines are also "lazy" - won't warn
    spawn(someMachine);

    It is recommended that all spawn(...)-ed actors are lazy, to avoid accidentally initializing them e.g., when reading machine.initialState or calculating otherwise pure transitions. In V5, this will be enforced.

  • c1f3d260 #1317 Thanks @Andarist! - Fixed a type returned by a raise action - it’s now RaiseAction<TEvent> | SendAction<TContext, AnyEventObject, TEvent> instead of RaiseAction<TEvent> | SendAction<TContext, TEvent, TEvent>. This makes it comaptible in a broader range of scenarios.
  • 8270d5a7 #1372 Thanks @christianchown! - Narrowed the ServiceConfig type definition to use a specific event type to prevent compilation errors on strictly-typed MachineOptions.
  • 01e3e2dc #1320 Thanks @davidkpiano! - The JSON definition for stateNode.invoke objects will no longer include the onDone and onError transitions, since those transitions are already merged into the transitions array. This solves the issue of reviving a serialized machine from JSON, where before, the onDone and onError transitions for invocations were wrongly duplicated.

4.11.0

Minor Changes

  • 36ed8d0a #1262 Thanks @Andarist! - Improved type inference for InvokeConfig['data']. This has required renaming data property on StateNode instances to doneData. This property was never meant to be a part of the public API, so we don’t consider this to be a breaking change.
  • 2c75ab82 #1219 Thanks @davidkpiano! - The resolved value of the invoke.data property is now available in the “invoke meta” object, which is passed as the 3rd argument to the service creator in options.services. This will work for all types of invoked services now, including promises, observables, and callbacks.

    const machine = createMachine({
      initial: 'pending',
      context: {
        id: 42
      },
      states: {
        pending: {
          invoke: {
            src: 'fetchUser',
            data: {
              userId: (context) => context.id
            },
            onDone: 'success'
          }
        },
        success: {
          type: 'final'
        }
      }
    },
    {
      services: {
        fetchUser: (ctx, _, { data }) => {
          return fetch(`some/api/user/${data.userId}`)
            .then(response => response.json());
        }
      }
    }
  • a6c78ae9 #1249 Thanks @davidkpiano! - New property introduced for eventless (transient) transitions: always, which indicates a transition that is always taken when in that state. Empty string transition configs for transient transitions are deprecated in favor of always:

    // ...
    states: {
      playing: {
    +   always: [
    +     { target: 'win', cond: 'didPlayerWin' },
    +     { target: 'lose', cond: 'didPlayerLose' },
    +   ],
        on: {
          // ⚠️ Deprecation warning
    -     '': [
    -       { target: 'win', cond: 'didPlayerWin' },
    -       { target: 'lose', cond: 'didPlayerLose' },
    -     ]
        }
      }
    }
    // ...

    The old empty string syntax ('': ...) will continue to work until V5.

Patch Changes

  • 36ed8d0a #1262 Thanks @Andarist! - StateMachine<any, any, any> is no longer a part of the InvokeConfig type, but rather it creates a union with InvokeConfig in places where it is needed. This change shouldn’t affect consumers’ code.

4.10.0

Minor Changes

  • 0133954 #1178 Thanks @davidkpiano! - The types for the send() and sendParent() action creators have been changed to fix the issue of only being able to send events that the machine can receive. In reality, a machine can and should send events to other actors that it might not be able to receive itself. See #711 for more information.
  • a1f1239 #1189 Thanks @davidkpiano! - Previously, state.matches(...) was problematic because it was casting state to never if it didn’t match the state value. This is now fixed by making the Typestate resolution more granular.
  • dbc6a16 #1183 Thanks @davidkpiano! - Actions from a restored state provided as a custom initial state to interpret(machine).start(initialState) are now executed properly. See #1174 for more information.

Patch Changes

  • 326db72 #1185 Thanks @Andarist! - Fixed an issue with invoked service not being correctly started if other service got stopped in a subsequent microstep (in response to raised or null event).
  • c3a496e #1160 Thanks @davidkpiano! - Delayed transitions defined using after were previously causing a circular dependency when the machine was converted using .toJSON(). This has now been fixed.
  • e16e48e #1153 Thanks @Andarist! - Fixed an issue with choose and pure not being able to use actions defined in options.
  • d496ecb #1165 Thanks @davidkpiano! - XState will now warn if you define an .onDone transition on the root node. Root nodes which are “done” represent the machine being in its final state, and can no longer accept any events. This has been reported as confusing in #1111.

4.9.1

Patch Changes

  • 8a97785 #1137 Thanks @davidkpiano! - Added docs for the choose() and pure() action creators, as well as exporting the pure() action creator in the actions object.
  • e65dee9 #1131 Thanks @wKovacs64! - Include the new choose action in the actions export from the xstate core package. This was missed in v4.9.0.

4.9.0

Minor Changes

  • f3ff150 #1103 Thanks @davidkpiano! - Simplify the TransitionConfigArray and TransitionConfigMap types in order to fix excessively deep type instantiation TypeScript reports. This addresses #1015.
  • 6c47b66 #1076 Thanks @Andarist! - Added support for conditional actions. It’s possible now to have actions executed based on conditions using following:

    entry: [
      choose([
        { cond: ctx => ctx > 100, actions: raise('TOGGLE') },
        {
          cond: 'hasMagicBottle',
          actions: [assign(ctx => ({ counter: ctx.counter + 1 }))]
        },
        { actions: ['fallbackAction'] }
      ])
    ];

    It works very similar to the if-else syntax where only the first matched condition is causing associated actions to be executed and the last ones can be unconditional (serving as a general fallback, just like else branch).

Patch Changes

  • 1a129f0 #1073 Thanks @Andarist! - Cleanup internal structures upon receiving termination events from spawned actors.
  • e88aa18 #1085 Thanks @Andarist! - Fixed an issue with data expressions of root’s final nodes being called twice.
  • 88b17b2 #1090 Thanks @rjdestigter! - This change carries forward the typestate type information encoded in the arguments of the following functions and assures that the return type also has the same typestate type information:

    • Cloned state machine returned by .withConfig.
    • .state getter defined for services.
    • start method of services.

4.8.0

Minor Changes

  • 55aa589 #960 Thanks @davidkpiano! - The machine can now be safely JSON-serialized, using JSON.stringify(machine). The shape of this serialization is defined in machine.schema.json and reflected in machine.definition.

    Note that onEntry and onExit have been deprecated in the definition in favor of entry and exit.

Patch Changes

4.7.8

Patch Changes

4.7.7

Patch Changes

  • c8db035 #936 Thanks @davidkpiano! - The escalate() action can now take in an expression, which will be evaluated against the context, event, and meta to return the error data.
  • 2a3fea1 #952 Thanks @davidkpiano! - The typings for the raise() action have been fixed to allow any event to be raised. This typed behavior will be refined in version 5, to limit raised events to those that the machine accepts.
  • f86d419 #957 Thanks @Andarist! - Fixed memory leak - each created service has been registered in internal map but it was never removed from it. Registration has been moved to a point where Interpreter is being started and it’s deregistered when it is being stopped.

4.7.6

Patch Changes

  • dae8818: Typestates are now propagated to interpreted services.

4.7.5

Patch Changes

  • 6b3d767: Fixed issue with delayed transitions scheduling a delayed event for each transition defined for a single delay.

4.7.4

Patch Changes

  • 9b043cd: The initial state is now cached inside of the service instance instead of the machine, which was the previous (faulty) strategy. This will prevent entry actions on initial states from being called more than once, which is important for ensuring that actors are not spawned more than once.

4.7.3

Patch Changes

  • 2b134eee: Fixed issue with events being forwarded to children after being processed by the current machine. Events are now always forwarded first.
  • 2b134eee: Fixed issue with not being able to spawn an actor when processing an event batch.