Skip to content

Releases: statelyai/xstate

@xstate/graph@2.0.0

29 May 22:23
7c4fd74
Compare
Choose a tag to compare

Major Changes

  • #4896 7c6e2ea Thanks @davidkpiano! - Test model path generation now has the option to allow duplicate paths by setting allowDuplicatePaths: true:

    const paths = model.getSimplePaths({
      allowDuplicatePaths: true
    });
    // a
    // a -> b
    // a -> b -> c
    // a -> d
    // a -> d -> e

    By default, allowDuplicatePaths is set to false:

    const paths = model.getSimplePaths();
    // a -> b -> c
    // a -> d -> e
  • #4896 7c6e2ea Thanks @davidkpiano! - The adjacencyMapToArray(…) helper function has been introduced, which converts an adjacency map to an array of { state, event, nextState } objects.

    import { getAdjacencyMap, adjacencyMapToArray } from '@xstate/graph';
    
    const machine = createMachine({
      initial: 'green',
      states: {
        green: {
          on: {
            TIMER: 'yellow'
          }
        },
        yellow: {
          on: {
            TIMER: 'red'
          }
        },
        red: {
          on: {
            TIMER: 'green'
          }
        }
      }
    });
    
    const arr = adjacencyMapToArray(getAdjacencyMap(machine));
    // [
    //   {
    //     "state": {value: "green", ... },
    //     "event": { type: "TIMER" },
    //     "nextState": { value: "yellow", ... }
    //   },
    //   {
    //     "state": {value: "yellow", ... },
    //     "event": { type: "TIMER" },
    //     "nextState": { value: "red", ... }
    //   },
    //   {
    //     "state": {value: "red", ... },
    //     "event": { type: "TIMER" },
    //     "nextState": { value: "green", ... }
    //   },
    //   {
    //     "state": {value: "green", ... },
    //     "event": { type: "TIMER" },
    //     "nextState": { value: "yellow", ... }
    //   },
    // ]
  • #4896 7c6e2ea Thanks @davidkpiano! - The traversalLimit option has been renamed to limit:

    model.getShortestPaths({
    - traversalLimit: 100
    + limit: 100
    });
  • #4233 3d96d0f95 Thanks @davidkpiano! - Remove getMachineShortestPaths and getMachineSimplePaths

    import {
    - getMachineShortestPaths,
    + getShortestPaths,
    - getMachineSimplePaths,
    + getSimplePaths
    } from '@xstate/graph';
    
    -const paths = getMachineShortestPaths(machine);
    +const paths = getShortestPaths(machine);
    
    -const paths = getMachineSimplePaths(machine);
    +const paths = getSimplePaths(machine);
  • #4238 b4f12a517 Thanks @davidkpiano! - The steps in the paths returned from functions like getShortestPaths(...) and getSimplePaths(...) have the following changes:

    • The step.event property now represents the event object that resulted in the transition to the step.state, not the event that comes before the next step.
    • The path.steps array now includes the target path.state as the last step.
      • Note: this means that path.steps always has at least one step.
    • The first step now has the { type: 'xstate.init' } event
  • #4896 7c6e2ea Thanks @davidkpiano! - The createTestMachine(…) function has been removed. Use a normal createMachine(…) or setup(…).createMachine(…) function instead to create machines for path generation.

  • #4896 7c6e2ea Thanks @davidkpiano! - The filter and stopCondition option for path generation has been renamed to stopWhen, which is used to stop path generation when a condition is met. This is a breaking change, but it is a more accurate name for the option.

    const shortestPaths = getShortestPaths(machine, {
      events: [{ type: 'INC' }],
    - filter: (state) => state.context.count < 5
    - stopCondition: (state) => state.context.count < 5
    + stopWhen: (state) => state.context.count === 5
    });
  • #4896 7c6e2ea Thanks @davidkpiano! - Path generation now supports input for actor logic:

    const model = createTestModel(
      setup({
        types: {
          input: {} as {
            name: string;
          },
          context: {} as {
            name: string;
          }
        }
      }).createMachine({
        context: (x) => ({
          name: x.input.name
        }),
        initial: 'checking',
        states: {
          checking: {
            always: [
              { guard: (x) => x.context.name.length > 3, target: 'longName' },
              { target: 'shortName' }
            ]
          },
          longName: {},
          shortName: {}
        }
      })
    );
    
    const path1 = model.getShortestPaths({
      input: { name: 'ed' }
    });
    
    expect(path1[0].steps.map((s) => s.state.value)).toEqual(['shortName']);
    
    const path2 = model.getShortestPaths({
      input: { name: 'edward' }
    });
    
    expect(path2[0].steps.map((s) => s.state.value)).toEqual(['longName']);
  • #4896 7c6e2ea Thanks @davidkpiano! - The test model "sync" methods have been removed, including:

    • testModel.testPathSync(…)
    • testModel.testStateSync(…)
    • testPath.testSync(…)

    The async methods should always be used instead.

    model.getShortestPaths().forEach(async (path) => {
    - model.testPathSync(path, {
    + await model.testPath(path, {
        states: { /* ... */ },
        events: { /* ... */ },
      });
    })

Minor Changes

  • #3727 5fb3c683d Thanks @Andarist! - exports field has been added to the package.json manifest. It limits what files can be imported from a package - it's no longer possible to import from files that are not considered to be a part of the public API.

Patch Changes

  • #4896 7c6e2ea Thanks @davidkpiano! - The @xstate/graph package now includes everything from @xstate/test.

  • #4308 af032db12 Thanks @davidkpiano! - Traversing state machines that have delayed transitions will now work as expected:

    const machine = createMachine({
      initial: 'a',
      states: {
        a: {
          after: {
            1000: 'b'
          }
        },
        b: {}
      }
    });
    
    const paths = getShortestPaths(machine); // works

@xstate/store@0.0.4

13 May 00:51
e405ef1
Compare
Choose a tag to compare

Patch Changes

  • #4890 6d92b7770 Thanks @davidkpiano! - The context type for createStoreWithProducer(producer, context, transitions) will now be properly inferred.

    const store = createStoreWithProducer(
      produce,
      {
        count: 0
      },
      {
        // ...
      }
    );
    
    store.getSnapshot().context;
    // BEFORE: StoreContext
    // NOW: { count: number }

xstate@5.13.0

04 May 23:28
e6b818c
Compare
Choose a tag to compare

Minor Changes

  • #4832 148d8fcef Thanks @cevr! - fromPromise now passes a signal into its creator function.

    const logic = fromPromise(({ signal }) =>
      fetch('https://api.example.com', { signal })
    );

    This will be called whenever the state transitions before the promise is resolved. This is useful for cancelling the promise if the state changes.

Patch Changes

  • #4876 3f6a73b56 Thanks @davidkpiano! - XState will now warn when calling built-in actions like assign, sendTo, raise, emit, etc. directly inside of a custom action. See https://stately.ai/docs/actions#built-in-actions for more details.

    const machine = createMachine({
      entry: () => {
        // Will warn:
        // "Custom actions should not call \`assign()\` directly, as it is not imperative. See https://stately.ai/docs/actions#built-in-actions for more details."
        assign({
          // ...
        });
      }
    });

xstate@5.12.0

24 Apr 19:45
a67e99f
Compare
Choose a tag to compare

Minor Changes

  • #4863 0696adc21 Thanks @davidkpiano! - Meta objects for state nodes and transitions can now be specified in setup({ types: … }):

    const machine = setup({
      types: {
        meta: {} as {
          layout: string;
        }
      }
    }).createMachine({
      initial: 'home',
      states: {
        home: {
          meta: {
            layout: 'full'
          }
        }
      }
    });
    
    const actor = createActor(machine).start();
    
    actor.getSnapshot().getMeta().home;
    // => { layout: 'full' }
    // if in "home" state

@xstate/vue@3.1.2

17 Apr 00:46
a36e3ec
Compare
Choose a tag to compare

Patch Changes

  • #4844 5aa6eb05c Thanks @davidkpiano! - The useSelector(…) hook from @xstate/react is now compatible with stores from @xstate/store.

    import { createStore } from '@xstate/store';
    import { useSelector } from '@xstate/react';
    
    const store = createStore(
      {
        count: 0
      },
      {
        inc: {
          count: (context) => context.count + 1
        }
      }
    );
    
    function Counter() {
      // Note that this `useSelector` is from `@xstate/react`,
      // not `@xstate/store/react`
      const count = useSelector(store, (state) => state.context.count);
    
      return (
        <div>
          <button onClick={() => store.send({ type: 'inc' })}>{count}</button>
        </div>
      );
    }

@xstate/svelte@3.0.3

17 Apr 00:46
a36e3ec
Compare
Choose a tag to compare

Patch Changes

  • #4844 5aa6eb05c Thanks @davidkpiano! - The useSelector(…) hook from @xstate/react is now compatible with stores from @xstate/store.

    import { createStore } from '@xstate/store';
    import { useSelector } from '@xstate/react';
    
    const store = createStore(
      {
        count: 0
      },
      {
        inc: {
          count: (context) => context.count + 1
        }
      }
    );
    
    function Counter() {
      // Note that this `useSelector` is from `@xstate/react`,
      // not `@xstate/store/react`
      const count = useSelector(store, (state) => state.context.count);
    
      return (
        <div>
          <button onClick={() => store.send({ type: 'inc' })}>{count}</button>
        </div>
      );
    }

@xstate/react@4.1.1

17 Apr 00:46
a36e3ec
Compare
Choose a tag to compare

Patch Changes

  • #4844 5aa6eb05c Thanks @davidkpiano! - The useSelector(…) hook from @xstate/react is now compatible with stores from @xstate/store.

    import { createStore } from '@xstate/store';
    import { useSelector } from '@xstate/react';
    
    const store = createStore(
      {
        count: 0
      },
      {
        inc: {
          count: (context) => context.count + 1
        }
      }
    );
    
    function Counter() {
      // Note that this `useSelector` is from `@xstate/react`,
      // not `@xstate/store/react`
      const count = useSelector(store, (state) => state.context.count);
    
      return (
        <div>
          <button onClick={() => store.send({ type: 'inc' })}>{count}</button>
        </div>
      );
    }

xstate@5.11.0

16 Apr 14:31
9118720
Compare
Choose a tag to compare

Minor Changes

  • #4806 f4e0ec48c Thanks @davidkpiano! - Inline actor logic is now permitted when named actors are present. Defining inline actors will no longer cause a TypeScript error:

    const machine = setup({
      actors: {
        existingActor: fromPromise(async () => {
          // ...
        })
      }
    }).createMachine({
      invoke: {
        src: fromPromise(async () => {
          // Inline actor
        })
        // ...
      }
    });

xstate@5.10.0

09 Apr 14:41
0ce95c2
Compare
Choose a tag to compare

Minor Changes

  • #4822 f7f1fbbf3 Thanks @davidkpiano! - The clock and logger specified in the options object of createActor(logic, options) will now propagate to all actors created within the same actor system.

    import { setup, log, createActor } from 'xstate';
    
    const childMachine = setup({
      // ...
    }).createMachine({
      // ...
      // Uses custom logger from root actor
      entry: log('something')
    });
    
    const parentMachine = setup({
      // ...
    }).createMachine({
      // ...
      invoke: {
        src: childMachine
      }
    });
    
    const actor = createActor(parentMachine, {
      logger: (...args) => {
        // custom logger for args
      }
    });
    
    actor.start();

@xstate/store@0.0.3

09 Apr 20:28
424ce97
Compare
Choose a tag to compare

Patch Changes