Skip to content

Releases: statelyai/xstate

@xstate/solid@0.2.1

06 Sep 07:22
5afff2c
Compare
Choose a tag to compare

Patch Changes

  • #5055 ad38c35c37 Thanks @SandroMaglione! - Updated types of useActor, useMachine, and useActorRef to require input when defined inside types/input.

    Previously even when input was defined inside types, useActor, useMachine, and useActorRef would not make the input required:

    const machine = setup({
      types: {
        input: {} as { value: number }
      }
    }).createMachine({});
    
    function App() {
      // Event if `input` is not defined, `useMachine` works at compile time, but risks crashing at runtime
      const _ = useMachine(machine);
      return <></>;
    }

    With this change the above code will show a type error, since input is now required:

    const machine = setup({
      types: {
        input: {} as { value: number }
      }
    }).createMachine({});
    
    function App() {
      const _ = useMachine(machine, {
        input: { value: 1 } // Now input is required at compile time!
      });
      return <></>;
    }

    This avoids runtime errors when forgetting to pass input when defined inside types.

@xstate/react@4.1.2

06 Sep 07:22
5afff2c
Compare
Choose a tag to compare

Patch Changes

  • #5055 ad38c35c37 Thanks @SandroMaglione! - Updated types of useActor, useMachine, and useActorRef to require input when defined inside types/input.

    Previously even when input was defined inside types, useActor, useMachine, and useActorRef would not make the input required:

    const machine = setup({
      types: {
        input: {} as { value: number }
      }
    }).createMachine({});
    
    function App() {
      // Event if `input` is not defined, `useMachine` works at compile time, but risks crashing at runtime
      const _ = useMachine(machine);
      return <></>;
    }

    With this change the above code will show a type error, since input is now required:

    const machine = setup({
      types: {
        input: {} as { value: number }
      }
    }).createMachine({});
    
    function App() {
      const _ = useMachine(machine, {
        input: { value: 1 } // Now input is required at compile time!
      });
      return <></>;
    }

    This avoids runtime errors when forgetting to pass input when defined inside types.

xstate@5.18.0

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/store@2.3.0

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/store@2.2.1

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

Patch Changes

xstate@5.17.4

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/store@2.2.0

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

xstate@5.17.3

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

Patch Changes

xstate@5.17.2

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

Patch Changes

@xstate/store@2.1.0

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!