Xstate Versions Save

Actor-based state management & orchestration for complex app logic.

[email protected]

1 week ago

Minor Changes

  • #4936 c58b36dc3 Thanks @davidkpiano! - Inspecting an actor system via actor.system.inspect(ev => …) now accepts a function or observer, and returns a subscription:

    const actor = createActor(someMachine);
    
    const sub = actor.system.inspect((inspectionEvent) => {
      console.log(inspectionEvent);
    });
    
    // Inspection events will be logged
    actor.start();
    actor.send({ type: 'anEvent' });
    
    // ...
    
    sub.unsubscribe();
    
    // Will no longer log inspection events
    actor.send({ type: 'someEvent' });
    
  • #4942 9caaa1f70 Thanks @boneskull! - DoneActorEvent and ErrorActorEvent now contain property actorId, which refers to the ID of the actor the event refers to.

  • #4935 2ac08b700 Thanks @davidkpiano! - All actor logic creators now support emitting events:

    Promise actors

    const logic = fromPromise(async ({ emit }) => {
      // ...
      emit({
        type: 'emitted',
        msg: 'hello'
      });
      // ...
    });
    

    Transition actors

    const logic = fromTransition((state, event, { emit }) => {
      // ...
      emit({
        type: 'emitted',
        msg: 'hello'
      });
      // ...
      return state;
    }, {});
    

    Observable actors

    const logic = fromObservable(({ emit }) => {
      // ...
    
      emit({
        type: 'emitted',
        msg: 'hello'
      });
    
      // ...
    });
    

    Callback actors

    const logic = fromCallback(({ emit }) => {
      // ...
      emit({
        type: 'emitted',
        msg: 'hello'
      });
      // ...
    });
    

Patch Changes

  • #4929 417f35a11 Thanks @boneskull! - Expose type UnknownActorRef for use when calling getSnapshot() on an unknown ActorRef.

@xstate/[email protected]

3 weeks ago

Major Changes

[email protected]

3 weeks ago

Patch Changes

[email protected]

1 month ago

Patch Changes

  • #4905 dbeafeb25 Thanks @davidkpiano! - You can now use a wildcard to listen for any emitted event from an actor:

    actor.on('*', (emitted) => {
      console.log(emitted); // Any emitted event
    });
    

@xstate/[email protected]

1 month ago

Patch Changes

@xstate/[email protected]

1 month ago

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/[email protected]

1 month ago

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 }
    

[email protected]

1 month ago

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({
          // ...
        });
      }
    });
    

[email protected]

2 months ago

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/[email protected]

2 months ago

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>
      );
    }