Skip to content

Releases: statelyai/xstate

[email protected]

30 Aug 14:01
26aabae
Compare
Choose a tag to compare

Minor Changes

  • #5042 54c9d9e6a4 Thanks @boneskull! - waitFor() now accepts a {signal: AbortSignal} in WaitForOptions

  • #5006 1ab974547f Thanks @davidkpiano! - The state value typings for setup state machine actors (setup({}).createMachine({ ... })) have been improved to represent the actual expected state values.

    const machine = setup({}).createMachine({
      initial: 'green',
      states: {
        green: {},
        yellow: {},
        red: {
          initial: 'walk',
          states: {
            walk: {},
            wait: {},
            stop: {}
          }
        },
        emergency: {
          type: 'parallel',
          states: {
            main: {
              initial: 'blinking',
              states: {
                blinking: {}
              }
            },
            cross: {
              initial: 'blinking',
              states: {
                blinking: {}
              }
            }
          }
        }
      }
    });
    
    const actor = createActor(machine).start();
    
    const stateValue = actor.getSnapshot().value;
    
    if (stateValue === 'green') {
      // ...
    } else if (stateValue === 'yellow') {
      // ...
    } else if ('red' in stateValue) {
      stateValue;
      // {
      //   red: "walk" | "wait" | "stop";
      // }
    } else {
      stateValue;
      // {
      //   emergency: {
      //     main: "blinking";
      //     cross: "blinking";
      //   };
      // }
    }

Patch Changes

  • #5054 853f6daa0b Thanks @davidkpiano! - The CallbackLogicFunction type (previously InvokeCallback) is now exported. This is the callback function that you pass into fromCallback(callbackLogicFn) to create an actor from a callback function.

    import { type CallbackLogicFunction } from 'xstate';
    
    // ...

@xstate/[email protected]

30 Aug 14:01
26aabae
Compare
Choose a tag to compare

Minor Changes

  • #5056 8c35da9a72 Thanks @steveadams! - You can now use the xstate/store package with SolidJS.

    Import useSelector from @xstate/store/solid. Select the data you want via useSelector(…) and send events using store.send(eventObject):

    import { donutStore } from './donutStore.ts';
    import { useSelector } from '@xstate/store/solid';
    
    function DonutCounter() {
      const donutCount = useSelector(donutStore, (state) => state.context.donuts);
    
      return (
        <div>
          <button onClick={() => donutStore.send({ type: 'addDonut' })}>
            Add donut ({donutCount()})
          </button>
        </div>
      );
    }

@xstate/[email protected]

19 Aug 13:05
59c6185
Compare
Choose a tag to compare

Patch Changes

[email protected]

15 Aug 15:45
3e5424d
Compare
Choose a tag to compare

Patch Changes

  • #5039 d6df8fb470 Thanks @Andarist! - Fixed an inference issue that prevented emit used directly in setup (or bare createMachine) to benefit from types.emitted types.

@xstate/[email protected]

15 Aug 15:45
3e5424d
Compare
Choose a tag to compare

Minor Changes

  • #5027 758a78711d Thanks @davidkpiano! - You can now inspect XState stores using the .inspect(inspector) method:

    import { someStore } from './someStore';
    
    someStore.inspect((inspEv) => {
      console.log(inspEv);
      // logs "@xstate.event" events and "@xstate.snapshot" events
      // whenever an event is sent to the store
    });
    // The "@xstate.actor" event is immediately logged

[email protected]

13 Aug 22:31
113cd56
Compare
Choose a tag to compare

Patch Changes

[email protected]

13 Aug 01:09
12bde7a
Compare
Choose a tag to compare

Patch Changes

@xstate/[email protected]

05 Aug 20:06
437738d
Compare
Choose a tag to compare

Minor Changes

  • #5020 e974797b0 Thanks @with-heart! - Added the EventFromStore utility type which extracts the type of events from a store:

    import { createStore, type EventFromStore } from '@xstate/store';
    
    const store = createStore(
      { count: 0 },
      {
        add: (context, event: { addend: number }) => ({
          count: context.count + event.addend
        }),
        multiply: (context, event: { multiplier: number }) => ({
          count: context.count * event.multiplier
        })
      }
    );
    
    type StoreEvent = EventFromStore<typeof store>;
    //   ^? { type: 'add'; addend: number } | { type: 'multiply'; multiplier: number }

    EventFromStore allows us to create our own utility types which operate on a store's event types.

    For example, we could create a type EventByType which extracts the specific type of store event where Type matches the event's type property:

    import { type EventFromStore, type Store } from '@xstate/store';
    
    /**
     * Extract the event where `Type` matches the event's `type` from the given
     * `Store`.
     */
    type EventByType<
      TStore extends Store<any, any>,
      // creates a type-safe relationship between `Type` and the `type` keys of the
      // store's events
      Type extends EventFromStore<TStore>['type']
    > = Extract<EventFromStore<TStore>, { type: Type }>;

    Here's how the type works with the store we defined in the first example:

    // we get autocomplete listing the store's event `type` values on the second
    // type parameter
    type AddEvent = EventByType<typeof store, 'add'>;
    //   ^? { type: 'add'; addend: number }
    
    type MultiplyEvent = EventByType<typeof store, 'multiply'>;
    //   ^? { type: 'multiply'; multiplier: number }
    
    // the second type parameter is type-safe, meaning we get a type error if the
    // value isn't a valid event `type`
    type DivideEvent = EventByType<typeof store, 'divide'>;
    // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)

    Building on that, we could create a type EventInputByType to extract a specific event's "input" type (the event type without the type property):

    import { type EventFromStore, type Store } from '@xstate/store';
    
    /**
     * Extract a specific store event's "input" type (the event type without the
     * `type` property).
     */
    type EventInputByType<
      TStore extends Store<any, any>,
      Type extends EventFromStore<TStore>['type']
    > = Omit<EventByType<TStore, Type>, 'type'>;

    And here's how EventInputByType works with our example store:

    type AddInput = EventInputByType<typeof store, 'add'>;
    //   ^? { addend: number }
    
    type MultiplyInput = EventInputByType<typeof store, 'multiply'>;
    //   ^? { multiplier: number }
    
    type DivideInput = EventInputByType<typeof store, 'divide'>;
    // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)

    Putting it all together, we can use EventInputByType to create a type-safe transition function for each of our store's defined events:

    import { createStore, type EventFromStore, type Store } from '@xstate/store';
    
    /**
     * Extract the event where `Type` matches the event's `type` from the given
     * `Store`.
     */
    type EventByType<
      TStore extends Store<any, any>,
      Type extends EventFromStore<TStore>['type']
    > = Extract<EventFromStore<TStore>, { type: Type }>;
    
    /**
     * Extract a specific store event's "input" type (the event type without the
     * `type` property).
     */
    type EventInputByType<
      TStore extends Store<any, any>,
      Type extends EventFromStore<TStore>['type']
    > = Omit<EventByType<TStore, Type>, 'type'>;
    
    const store = createStore(
      { count: 0 },
      {
        add: (context, event: { addend: number }) => ({
          count: context.count + event.addend
        }),
        multiply: (context, event: { multiplier: number }) => ({
          count: context.count * event.multiplier
        })
      }
    );
    
    const add = (input: EventInputByType<typeof store, 'add'>) =>
      store.send({ type: 'add', addend: input.addend });
    
    add({ addend: 1 }); // sends { type: 'add', addend: 1 }
    
    const multiply = (input: EventInputByType<typeof store, 'multiply'>) =>
      store.send({ type: 'multiply', multiplier: input.multiplier });
    
    multiply({ multiplier: 2 }); // sends { type: 'multiply', multiplier: 2 }

    Happy typing!

[email protected]

31 Jul 16:10
c62e1f8
Compare
Choose a tag to compare

Patch Changes

  • #5009 51d4c4fc5 Thanks @davidkpiano! - The internal types for StateMachine<...> have been improved so that all type params are required, to prevent errors when using the types. This fixes weird issues like #5008.

[email protected]

30 Jul 19:13
eac555e
Compare
Choose a tag to compare

Minor Changes

  • #4979 a0e9ebcef Thanks @davidkpiano! - State IDs are now strongly typed as keys of snapshot.getMeta() for state machine actor snapshots.

    const machine = setup({
      // ...
    }).createMachine({
      id: 'root',
      initial: 'parentState',
      states: {
        parentState: {
          meta: {},
          initial: 'childState',
          states: {
            childState: {
              meta: {}
            },
            stateWithId: {
              id: 'state with id',
              meta: {}
            }
          }
        }
      }
    });
    
    const actor = createActor(machine);
    
    const metaValues = actor.getSnapshot().getMeta();
    
    // Auto-completed keys:
    metaValues.root;
    metaValues['root.parentState'];
    metaValues['root.parentState.childState'];
    metaValues['state with id'];
    
    // @ts-expect-error
    metaValues['root.parentState.stateWithId'];
    
    // @ts-expect-error
    metaValues['unknown state'];

Patch Changes

  • #5002 9877d548b Thanks @davidkpiano! - Fix an issue where clearTimeout(undefined) was sometimes being called, which can cause errors for some clock implementations. See #5001 for details.