xstate

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

MIT License

Downloads
14.3M
Stars
26.2K
Committers
362

Bot releases are visible (Hide)

xstate - @xstate/[email protected]

Published by github-actions[bot] 10 months ago

Patch Changes

xstate - @xstate/[email protected]

Published by github-actions[bot] 10 months ago

Patch Changes

xstate - [email protected]

Published by github-actions[bot] 10 months ago

Minor Changes

  • #4547 8e8d2ba38 Thanks @davidkpiano! - Add assertEvent(...) to help provide strong typings for events that can't be easily inferred, such as events in entry and exit actions, or in invoke.input.

    The assertEvent(event, 'someType') function will throw if the event is not the expected type. This ensures that the event is guaranteed to have that type, and assumes that the event object has the expected payload (naturally enforced by TypeScript).

    // ...
    entry: ({ event }) => {
      assertEvent(event, 'greet');
      // event is { type: 'greet'; message: string }
    
      assertEvent(event, ['greet', 'notify']);
      // event is { type: 'greet'; message: string }
      // or { type: 'notify'; message: string; level: 'info' | 'error' }
    },
    exit: ({ event }) => {
      assertEvent(event, 'doNothing');
      // event is { type: 'doNothing' }
    }
    

Patch Changes

  • #4586 97f1cbd5f Thanks @Andarist! - Fixed an issue with ancestors of the default history target that lie outside of the transition domain being incorrectly entered.
xstate - [email protected]

Published by github-actions[bot] 10 months ago

Patch Changes

xstate - [email protected]

Published by github-actions[bot] 11 months ago

Minor Changes

  • #4198 ca58904ad Thanks @davidkpiano! - Introduce toPromise(actor), which creates a promise from an actor that resolves with the actor snapshot's output when done, or rejects with the actor snapshot's error when it fails.

    import { createMachine, createActor, toPromise } from 'xstate';
    
    const machine = createMachine({
      // ...
      states: {
        // ...
        done: { type: 'final', output: 42 }
      }
    });
    
    const actor = createActor(machine);
    
    actor.start();
    
    const output = await toPromise(actor);
    
    console.log(output);
    // => 42
    

Patch Changes

  • #4568 a5c55fae2 Thanks @Andarist! - Fixed an issue with spawn within assign not returning a narrowed down ActorRef type on TypeScrip 5.0

  • #4570 c11127336 Thanks @Andarist! - Fixed an issue that caused a complete listener to be called instead of the error one when the actor was subscribed after being stopped.

xstate - [email protected]

Published by github-actions[bot] 11 months ago

Minor Changes

  • #4497 d7f220225 Thanks @davidkpiano! - Internal: abstract the scheduler for delayed events so that it is handled centrally by the system.

Patch Changes

xstate - @xstate/[email protected]

Published by github-actions[bot] 11 months ago

Patch Changes

  • #4497 d7f220225 Thanks @davidkpiano! - Fix an issue where after transitions do not work in React strict mode. Delayed events (including from after transitions) should now work as expected in all React modes.
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Patch Changes

xstate - [email protected]

Published by github-actions[bot] 11 months ago

Patch Changes

xstate - @xstate/[email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #4507 9ea542c34 Thanks @Andarist! - The useMachine(machine) hook now returns { snapshot, send, actorRef } instead of { state, send, service }:

    const {
    - state,
    + snapshot,
      send,
    - service
    + actorRef
    } = useMachine(machine);
    
  • #4265 1153b3f9a Thanks @davidkpiano! - FSM-related functions have been removed.

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.
  • #4507 9ea542c34 Thanks @Andarist! - The useActorRef(logic) and useActor(logic) hooks have been added.
xstate - @xstate/[email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #4288 cfdf754f8 Thanks @davidkpiano! - The useMachine(machine) hook now returns { snapshot, send, actorRef } instead of { state, send, service }:

    const {
    - state,
    + snapshot,
      send,
    - service
    + actorRef
    } = useMachine(machine);
    
  • #3148 7a68cbb61 Thanks @davidkpiano! - Removed getSnapshot parameter from composables. It is expected that the received actorRef has to have a getSnapshot method on it that can be used internally.

  • #4265 1153b3f9a Thanks @davidkpiano! - FSM-related functions have been removed.

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.
  • #4288 cfdf754f8 Thanks @davidkpiano! - The useInterpret(machine) and useSpawn(machine) hooks have been removed; use the useActorRef(machine) hook instead.
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • d3d6149c7: If context types are specified in the machine config, the context property will now be required:

    // ❌ TS error
    createMachine({
      types: {} as {
        context: { count: number };
      }
      // Missing context property
    });
    
    // ✅ OK
    createMachine({
      types: {} as {
        context: { count: number };
      },
      context: {
        count: 0
      }
    });
    
  • d3d6149c7: - The third argument of machine.transition(state, event) has been removed. The context should always be given as part of the state.

    • There is a new method: machine.microstep(snapshot, event) which returns the resulting intermediate MachineSnapshot object that represents a single microstep being taken when transitioning from snapshot via the event. This is the MachineSnapshot that does not take into account transient transitions nor raised events, and is useful for debugging.
    • The state.events property has been removed from the State object
    • The state.historyValue property now more closely represents the original SCXML algorithm, and is a mapping of state node IDs to their historic descendent state nodes. This is used for resolving history states, and should be considered internal.
    • The stateNode.isTransient property is removed from StateNode.
    • The .initial property of a state node config object can now contain executable content (i.e., actions):
    // ...
    initial: {
      target: 'someTarget',
      actions: [/* initial actions */]
    }
    
    • Assign actions (via assign()) will now be executed "in order", rather than automatically prioritized. They will be evaluated after previously defined actions are evaluated, and actions that read from context will have those intermediate values applied, rather than the final resolved value of all assign() actions taken, which was the previous behavior.

    This shouldn't change the behavior for most state machines. To maintain the previous behavior, ensure that assign() actions are defined before any other actions.

  • d3d6149c7: An error will be thrown if an initial state key is not specified for compound state nodes. For example:

    const lightMachine = createMachine({
      id: 'light',
      initial: 'green',
      states: {
        green: {},
        yellow: {},
        red: {
          // Forgotten initial state:
          // initial: 'walk',
          states: {
            walk: {},
            wait: {}
          }
        }
      }
    });
    

    You will get the error:

    No initial state specified for state node "#light.red". Try adding { initial: "walk" } to the state config.
    
  • d3d6149c7: IDs for delayed events are no longer derived from event types so this won't work automatically:

    entry: raise({ type: 'TIMER' }, { delay: 200 });
    exit: cancel('TIMER');
    

    Please use explicit IDs:

    entry: raise({ type: 'TIMER' }, { delay: 200, id: 'myTimer' });
    exit: cancel('myTimer');
    
  • d3d6149c7: Removed State#toStrings method.

  • d3d6149c7: The machine's context is now restricted to an Record<string, any>. This was the most common usage, but now the typings prevent context from being anything but an object:

    const machine = createMachine({
      // This will produce the TS error:
      // "Type 'string' is not assignable to type 'object | undefined'"
      context: 'some string'
    });
    

    If context is undefined, it will now default to an empty object {}.

  • d3d6149c7: Actors are now always part of a "system", which is a collection of actors that can communicate with each other. Systems are implicitly created, and can be used to get and set references to any actor in the system via the systemId prop:

    const machine = createMachine({
      // ...
      invoke: {
        src: emailMachine,
        // Registers `emailMachine` as `emailer` on the system
        systemId: 'emailer'
      }
    });
    
    const machine = createMachine({
      // ...
      entry: assign({
        emailer: (ctx, ev, { spawn }) =>
          spawn(emailMachine, { systemId: 'emailer' })
      })
    });
    

    Any invoked/spawned actor that is part of a system will be able to reference that actor:

    const anotherMachine = createMachine({
      // ...
      entry: sendTo(
        (ctx, ev, { system }) => {
          return system.get('emailer');
        },
        { type: 'SEND_EMAIL', subject: 'Hello', body: 'World' }
      )
    });
    

    Each top-level createActor(...) call creates a separate implicit system. In this example example, actor1 and actor2 are part of different systems and are unrelated:

    // Implicit system
    const actor1 = createActor(machine).start();
    
    // Another implicit system
    const actor2 = createActor(machine).start();
    
  • d3d6149c7: external property on transitions has been renamed to reenter

  • d3d6149c7: The interpreter.onStop(...) method has been removed. Use an observer instead via actorRef.subscribe({ complete() { ... } }) instead.

  • d3d6149c7: Removed MachineSnapshot['nextEvents'].

  • d3d6149c7: Renamed machine.withConfig(...) to machine.provide(...).

  • d3d6149c7: Removed third parameter (context) from Machine's transition method. If you want to transition with a particular context value you should create appropriate MachineSnapshot using machine.resolveState. So instead of this - machine.transition('green', { type: 'TIMER' }, { elapsed: 100 }), you should do this - machine.transition(machine.resolveState({ value: 'green', context: { elapsed: 100 } }), { type: 'TIMER' }).

  • d3d6149c7: Sending a string event to actorRef.send('some string') will now throw a proper error message.

  • d3d6149c7: The self actor reference is now available in all action metas. This makes it easier to reference the "self" ActorRef so that actions such as sendTo can include it in the event payload:

    // Sender
    actions: sendTo('somewhere', (ctx, ev, { self }) => ({
      type: 'EVENT',
      ref: self
    })),
    
    // ...
    
    // Responder
    actions: sendTo((ctx, ev) => ev.ref, ...)
    
  • d3d6149c7: isState/isStateConfig were replaced by isMachineSnapshot. Similarly, AnyState type was deprecated and it's replaced by AnyMachineSnapshot type.

  • d3d6149c7: All actor snapshots now have a consistent, predictable shape containing these common properties:

    • status: 'active' | 'done' | 'error' | 'stopped'
    • output: The output data of the actor when it has reached status: 'done'
    • error: The error thrown by the actor when it has reached status: 'error'
    • context: The context of the actor

    This makes it easier to work with actors in a consistent way, and to inspect their snapshots.

    const promiseActor = fromPromise(async () => {
      return 42;
    });
    
    // Previously number | undefined
    // Now a snapshot object with { status, output, error, context }
    const promiseActorSnapshot = promiseActor.getSnapshot();
    
    if (promiseActorSnapshot.status === 'done') {
      console.log(promiseActorSnapshot.output); // 42
    }
    
  • d3d6149c7: Restoring persisted state is now done by passing the state into the snapshot: ... property of the createActor options argument:

    -interpret(machine).start(state);
    +createActor(machine, { snapshot }).start();
    

    The persisted snapshot is obtained from an actor by calling actor.getPersistedSnapshot():

    const actor = createActor(machine).start();
    
    const persistedSnapshot = actor.getPersistedSnapshot();
    
    // ...
    
    const restoredActor = createActor(machine, {
      snapshot: persistedSnapshot
    }).start();
    
  • d3d6149c7: - The execute option for an interpreted service has been removed. If you don't want to execute actions, it's recommended that you don't hardcode implementation details into the base machine that will be interpreted, and extend the machine's options.actions instead. By default, the interpreter will execute all actions according to SCXML semantics (immediately upon transition).

    • Dev tools integration has been simplified, and Redux dev tools support is no longer the default. It can be included from xstate/devTools/redux:
    import { createActor } from 'xstate';
    import { createReduxDevTools } from 'xstate/devTools/redux';
    
    const service = createActor(someMachine, {
      devTools: createReduxDevTools({
        // Redux Dev Tools options
      })
    });
    

    By default, dev tools are attached to the global window.__xstate__ object:

    const service = createActor(someMachine, {
      devTools: true // attaches via window.__xstate__.register(service)
    });
    

    And creating your own custom dev tools adapter is a function that takes in the actorRef:

    const myCustomDevTools = (actorRef) => {
      console.log('Got a actorRef!');
    
      actorRef.subscribe((state) => {
        // ...
      });
    };
    
    const actorRef = createActor(someMachine, {
      devTools: myCustomDevTools
    });
    
    • These handlers have been removed, as they are redundant and can all be accomplished with .onTransition(...) and/or .subscribe(...):

      • actorRef.onEvent()
      • actorRef.onSend()
      • actorRef.onChange()
    • The actorRef.send(...) method no longer returns the next state. It is a void function (fire-and-forget).

    • The actorRef.sender(...) method has been removed as redundant. Use actorRef.send(...) instead.

  • d3d6149c7: The output data on final states is now specified as .output instead of .data:

    const machine = createMachine({
      // ...
      states: {
        // ...
        success: {
    -     data: { message: 'Success!' }
    +     output: { message: 'Success!' }
        }
      }
    })
    
  • d3d6149c7: Support for getters as a transition target (instead of referencing state nodes by ID or relative key) has been removed.

    The Machine() and createMachine() factory functions no longer support passing in context as a third argument.

    The context property in the machine configuration no longer accepts a function for determining context (which was introduced in 4.7). This might change as the API becomes finalized.

    The activities property was removed from State objects, as activities are now part of invoke declarations.

    The state nodes will not show the machine's version on them - the version property is only available on the root machine node.

  • d3d6149c7: The in: ... property for transitions is removed and replaced with guards. It is recommended to use stateIn() and not(stateIn()) guard creators instead:

    + import { stateIn } from 'xstate/guards';
    
    // ...
    on: {
      SOME_EVENT: {
        target: 'somewhere',
    -   in: '#someState'
    +   guard: stateIn('#someState')
      }
    }
    // ...
    
  • d3d6149c7: Removed Actor['status'] from publicly available properties.

  • d3d6149c7: All builtin action creators (assign, sendTo, etc) are now returning functions. They exact shape of those is considered an implementation detail of XState and users are meant to only pass around the returned values.

  • d3d6149c7: Autoforwarding events is no longer supported and the autoForward property has been removed.

    Instead of autoforwarding, events should be explicitly sent to actors:

    invoke: {
      id: 'child',
      src: 'someSrc',
    - autoForward: true
    },
    // ...
    on: {
      // ...
    + EVENT_TO_FORWARD: {
    +   actions: sendTo('child', (_, event) => event)
    + }
    }
    
  • d3d6149c7: The machine .schema property is now .types:

    const machine = createMachine({
      // schema: { ... }
      types: {} as {
        context: { ... };
        events: { ... };
        // ...
      }
    });
    

    And the .tsTypes property is now .types.typegen:

    const machine = createMachine({
      // tsTypes: { ... }
      types: {} as {
        typegen: {};
        context: { ... };
        events: { ... };
        // ...
      }
    });
    
  • d3d6149c7: Returning promises when creating a callback actor doesn't work anymore. Only cleanup functions can be returned now (or undefined).

  • d3d6149c7: There is now support for higher-level guards, which are guards that can compose other guards:

    • and([guard1, guard2, /* ... */]) returns true if all guards evaluate to truthy, otherwise false
    • or([guard1, guard2, /* ... */]) returns true if any guard evaluates to truthy, otherwise false
    • not(guard1) returns true if a single guard evaluates to false, otherwise true
    import { and, or, not } from 'xstate/guards';
    
    const someMachine = createMachine({
      // ...
      on: {
        EVENT: {
          target: 'somewhere',
          guard: and([
            'stringGuard',
            or([{ type: 'anotherGuard' }, not(() => false)])
          ])
        }
      }
    });
    
  • d3d6149c7: The .send(...) method on actorRef.send(...) now requires the first argument (the event to send) to be an object; that is, either:

    • an event object (e.g. { type: 'someEvent' })
    • an SCXML event object.

    The second argument (payload) is no longer supported, and should instead be included within the object:

    -actorRef.send('SOME_EVENT')
    +actorRef.send({ type: 'SOME_EVENT' })
    
    -actorRef.send('EVENT', { some: 'payload' })
    +actorRef.send({ type: 'EVENT', some: 'payload' })
    
  • d3d6149c7: Actions and guards that follow eventless transitions will now receive the event that triggered the transition instead of a "null" event ({ type: '' }), which no longer exists:

    // ...
    states: {
      a: {
        on: {
          SOME_EVENT: 'b'
        }
      },
      b: {
        always: 'c'
      },
      c: {
        entry: [({ event }) => {
          // event.type is now "SOME_EVENT", not ""
        }]
      }
    }
    // ...
    
  • d3d6149c7: You can now add a systemId to spawned actors to reference them anywhere in the system.

    const machine = createMachine({
      // ...
      context: ({ spawn }) => ({
        actorRef: spawn(
          createMachine({
            // ...
          }),
          { systemId: 'actorRef' }
        )
      })
    });
    
  • d3d6149c7: Reading the initial state from an actor via actorRef.initialState is removed. Use actorRef.getSnapshot() instead.

  • d3d6149c7: machine.initialState has been removed, you can use machine.getInitialState(...) instead

  • d3d6149c7: Target resolution improvements: targeting sibling nodes from the root is no longer valid, since the root node has no siblings:

    createMachine({
      id: 'direction',
      initial: 'left',
      states: {
        left: {},
        right: {}
      },
      on: {
    -   LEFT_CLICK: 'left',
    +   LEFT_CLICK: '.left'
      }
    });
    
  • d3d6149c7: The createActor(...) function now accepts input in the second argument, which passes input data in the "xstate.init" event:

    const greetMachine = createMachine({
      context: ({ input }) => ({
        greeting: `Hello ${input.name}!`
      }),
      entry: (_, event) => {
        event.type; // 'xstate.init'
        event.input; // { name: 'David' }
      }
      // ...
    });
    
    const actor = createActor(greetMachine, {
      // Pass input data to the machine
      input: { name: 'David' }
    }).start();
    
  • d3d6149c7: Invoked actors can now be deeply persisted and restored. When the persisted state of an actor is obtained via actorRef.getPersistedSnapshot(), the states of all invoked actors are also persisted, if possible. This state can be restored by passing the persisted state into the snapshot: ... property of the createActor options argument:

    -createActor(machine).start(state);
    +createActor(machine, { snapshot }).start();
    
  • d3d6149c7: Atomic and parallel states should no longer be reentered when the transition target doesn't escape them. You can get the reentering behavior by configuring reenter: true for the transition.

  • d3d6149c7: Restored state will no longer contain actions, since they are assumed to have already been executed. Actions will not be replayed.

    If you want to replay actions when restoring state, it is recommended to use an event sourcing approach.

  • d3d6149c7: The in: '...' transition property can now be replaced with stateIn(...) and stateNotIn(...) guards, imported from xstate/guards:

    import {
      createMachine,
    + stateIn
    } from 'xstate/guards';
    
    const machine = createMachine({
      // ...
      on: {
        SOME_EVENT: {
          target: 'anotherState',
    -     in: '#someState',
    +     cond: stateIn('#someState')
        }
      }
    })
    

    The stateIn(...) and stateNotIn(...) guards also can be used the same way as snapshot.matches(...):

    // ...
    SOME_EVENT: {
      target: 'anotherState',
      cond: stateNotIn({ red: 'stop' })
    }
    

    An error will now be thrown if the assign(...) action is executed when the context is undefined. Previously, there was only a warning.

    Error events raised by the machine will be thrown if there are no error listeners registered on an actor via actorRef.subscribe({ error: () => {} }).

  • d3d6149c7: Action/actor/delay/guard arguments are now consolidated into a single object argument. This is a breaking change for all of those things that are called with arguments.

    assign({
    - count: (context, event) => {
    + count: ({ context, event }) => {
        return context.count + event.value;
      }
    })
    
  • d3d6149c7: Eventless transitions must now be specified in the always: { ... } object and not in the on: { ... } object:

    someState: {
      on: {
        // Will no longer work
    -   '': { target: 'anotherState' }
      },
    + always: { target: 'anotherState' }
    }
    
  • d3d6149c7: Removed the ability to pass a string value directly to invoke. To migrate you should use the object version of invoke:

    -invoke: 'myActor'
    +invoke: { src: 'myActor' }
    
  • d3d6149c7: machine.transition(...) and machine.getInitialState(...) require now an actorScope argument

  • d3d6149c7: All events automatically generated by XState will now be prefixed by xstate.. Naming scheme changed slightly as well, for example done.invoke.* events became xstate.done.actor.* events.

  • d3d6149c7: The escalate() action is removed. Just throw an error normally.

  • d3d6149c7: The actor.onTransition(...) method has been removed in favor of .subscribe(...)

     const actor = interpret(machine)
    -  .onTransition(...)
    -  .start();
    +actor.subscribe(...);
    +actor.start();
    
  • d3d6149c7: Observing an actor via actorRef.subscribe(...) no longer immediately receives the current snapshot. Instead, the current snapshot can be read from actorRef.getSnapshot(), and observers will receive snapshots only when a transition in the actor occurs.

    const actorRef = createActor(machine);
    actorRef.start();
    
    // Late subscription; will not receive the current snapshot
    actorRef.subscribe((state) => {
      // Only called when the actor transitions
      console.log(state);
    });
    
    // Instead, current snapshot can be read at any time
    console.log(actorRef.getSnapshot());
    
  • d3d6149c7: Actors can no longer be stopped directly by calling actor.stop(). They can only be stopped from its parent internally (which might happen when you use stop action or automatically when a machine leaves the invoking state). The root actor can still be stopped since it has no parent.

  • d3d6149c7: The matchState(...) helper function is removed.

  • d3d6149c7: Parameterized actions now require a params property:

    // ...
    entry: [
      {
        type: 'greet',
    -   message: 'Hello'
    +   params: { message: 'Hello' }
      }
    ]
    // ...
    
  • d3d6149c7: The history resolution algorithm has been refactored to closely match the SCXML algorithm, which changes the shape of state.historyValue to map history state node IDs to their most recently resolved target state nodes.

  • d3d6149c7: Custom action objects and guard objects are now expected to put extra parameters on the params property:

    actions: {
      type: 'sendMessage',
    - message: 'hello'
    + params: {
    +   message: 'hello'
    + }
    }
    guard: {
      type: 'exists',
    - prop: 'user'
    + params: {
    +   prop: 'user'
    + }
    }
    
  • d3d6149c7: The strict: true option for machine config has been removed.

  • d3d6149c7: Removed the ability to define delayed transitions using an array. Only object variant is supported now:

    createMachine({
      initial: 'a',
      states: {
        a: {
          after: {
            10000: 'b',
            noon: 'c'
          }
        }
        // ...
      }
    });
    
  • d3d6149c7: Removed State['transitions'].

  • d3d6149c7: Removed the deprecated send action creator. Please use sendTo when sending events to other actors or raise when sending to itself.

  • d3d6149c7: The createEmptyActor() function has been added to make it easier to create actors that do nothing ("empty" actors). This is useful for testing, or for some integrations such as useActor(actor) in @xstate/react that require an actor:

    import { createEmptyActor } from 'xstate';
    
    const SomeComponent = (props) => {
      // props.actor may be undefined
      const [state, send] = useActor(props.actor ?? createEmptyActor());
    
      // ...
    };
    
  • d3d6149c7: machine.transition no longer accepts state values. You have to resolve the state value to a State before passing it to machine.transition

  • d3d6149c7: Removed deferEvents from the actor options.

  • d3d6149c7: The state.history property has been removed. This does not affect the machine "history" mechanism.

    Storing previous state should now be done explicitly:

    let previousSnapshot;
    
    const actorRef = createActor(someMachine);
    actorRef.subscribe((snapshot) => {
      // previousSnapshot represents the last snapshot here
    
      // ...
    
      // update the previous snapshot at the end
      previousSnapshot = snapshot;
    });
    actorRef.start();
    
  • d3d6149c7: All errors caught while executing the actor should now consistently include the error in its snapshot.error and should be reported to the closest error listener.

  • d3d6149c7: You can now import the following from xstate:

    import {
      // actions
      // sendTo (removed)
      pure,
    
      // interpret helpers
      waitFor,
    
      // actor functions
      fromPromise,
      fromObservable,
      fromCallback,
      fromEventObservable,
      fromTransition,
    
      // guard functions
      stateIn,
      not,
      and,
      or
    }
    

    The send action was removed from exports; use sendTo(...) or raise(...) instead.

  • d3d6149c7: BREAKING: The cond property in transition config objects has been renamed to guard. This unifies terminology for guarded transitions and guard predicates (previously called "cond", or "conditional", predicates):

    someState: {
      on: {
        EVENT: {
          target: 'anotherState',
    -     cond: 'isValid'
    +     guard: 'isValid'
        }
      }
    }
    
  • d3d6149c7: The Machine() function has been removed. Use the createMachine() function instead.

    -import { Machine } from 'xstate';
    +import { createMachine } from 'xstate';
    
    -const machine = Machine({
    +const machine = createMachine({
      // ...
    });
    
  • d3d6149c7: The interpreter.onError(...) method has been removed. Use interpreter.subscribe({ error(err) => { ... } }) instead.

  • d3d6149c7: Actions are no longer called with state

  • d3d6149c7: spawn is no longer importable from xstate. Instead you get it in assign like this:

    assign((ctx, ev, { spawn }) => {
      return {
        ...ctx,
        actorRef: spawn(promiseActor)
      };
    });
    

    In addition to that, you can now spawn actors defined in your implementations object, in the same way that you were already able to do that with invoke. To do that just reference the defined actor like this:

    spawn('promiseActor');
    
  • d3d6149c7: State class has been removed and replaced by MachineSnapshot object. They largely have the same properties and methods. On of the main noticeable results of this change is that you can no longer check state instanceof State.

  • d3d6149c7: Guard arguments are now consolidated into a single object argument. This is a breaking change for all guards that are called with arguments.

    - guard: (context, event) => {
    + guard: ({ context, event }) => {
      return context.count + event.value > 10;
    }
    
  • d3d6149c7: The service.batch(events) method is no longer available.

  • d3d6149c7: The StateSchema type has been removed from all generic type signatures.

  • d3d6149c7: Removed State['_internalQueue'].

  • d3d6149c7: EmittedFrom type helper has been renamed to SnapshotFrom.

  • d3d6149c7: The fromReducer(...) function is now called fromTransition(...).

  • d3d6149c7: The pure() and choose() action creators have been removed, in favor of the more flexible enqueueActions() action creator:

    entry: [
      // pure(() => {
      //   return [
      //     'action1',
      //     'action2'
      //   ]
      // }),
      enqueueActions(({ enqueue }) => {
        enqueue('action1');
        enqueue('action2');
      })
    ];
    
    entry: [
      // choose([
      //   {
      //     guard: 'someGuard',
      //     actions: ['action1', 'action2']
      //   }
      // ]),
      enqueueActions(({ enqueue, check }) => {
        if (check('someGuard')) {
          enqueue('action1');
          enqueue('action2');
        }
      })
    ];
    
  • d3d6149c7: Changed behavior of always transitions. Previously they were always selected after selecting any transition (including the always transitions). Because of that it was relatively easy to create an infinite loop using them.

    Now they are no longer selected if the preceeding transition doesn't change the state of a machine.

  • d3d6149c7: Breaking: The state.children property is now a mapping of invoked actor IDs to their ActorRef instances.

    Breaking: The way that you interface with invoked/spawned actors is now through ActorRef instances. An ActorRef is an opaque reference to an Actor, which should be never referenced directly.

    Breaking: The origin of an SCXML.Event is no longer a string, but an ActorRef instance.

  • d3d6149c7: The services option passed as the second argument to createMachine(config, options) is renamed to actors. Each value in actors should be a function that takes in context and event and returns a [behavior](TODO: link) for an actor. The provided behavior creators are:

    • fromMachine
    • fromPromise
    • fromCallback
    • fromObservable
    • fromEventObservable
    import { createMachine } from 'xstate';
    +import { fromPromise } from 'xstate/actors';
    
    const machine = createMachine(
      {
        // ...
        invoke: {
          src: 'fetchFromAPI'
        }
      },
      {
    -   services: {
    +   actors: {
    -     fetchFromAPI: (context, event) => {
    +     fetchFromAPI: (context, event) => fromPromise(() => {
            // ... (return a promise)
          })
        }
      }
    );
    
  • d3d6149c7: The error event (type: 'xstate.error.*') now has the error data on the event.error instead of event.data:

    // ...
    invoke: {
      src: 'someSrc',
      onError: {
        actions: ({ event }) => {
    -     event.data;
    +     event.error;
        }
      }
    }
    
  • d3d6149c7: _event has been removed from all APIs and types. It was a wrapper structure containing the event that users were using directly.

  • d3d6149c7: Actor types can now be specified in the .types property of createMachine:

    const fetcher = fromPromise(() => fetchUser());
    
    const machine = createMachine({
      types: {} as {
        actors: {
          src: 'fetchData'; // src name (inline behaviors ideally inferred)
          id: 'fetch1' | 'fetch2'; // possible ids (optional)
          logic: typeof fetcher;
        };
      },
      invoke: {
        src: 'fetchData', // strongly typed
        id: 'fetch2', // strongly typed
        onDone: {
          actions: ({ event }) => {
            event.output; // strongly typed as { result: string }
          }
        },
        input: { foo: 'hello' } // strongly typed
      }
    });
    
  • d3d6149c7: Interpreter['off'] method has been removed.

  • d3d6149c7: .nextState method has been removed from the Interpreter. State#can can be used to check if sending a particular event would lead to a state change.

  • d3d6149c7: Support for compound string state values has been dropped from Machine's transition method. It's no longer allowed to call transition like this - machine.transition('a.b', { type: 'NEXT' }), instead it's required to use "state value" representation like this - machine.transition({ a: 'b' }, { type: 'NEXT' }).

  • d3d6149c7: - Breaking: activities removed (can be invoked)

    Since activities can be considered invoked services, they can be implemented as such. Activities are services that do not send any events back to the parent machine, nor do they receive any events, other than a "stop" signal when the parent changes to a state where the activity is no longer active. This is modeled the same way as a callback service is modeled.

  • d3d6149c7: Removed previously deprecated config properties: onEntry, onExit, parallel and forward.

  • d3d6149c7: The state._sessionid property has been removed. It should be obtained directly from the actor: actor.sessionId.

  • d3d6149c7: The system can now be accessed in all available actor logic creator functions:

    fromPromise(({ system }) => { ... });
    
    fromTransition((state, event, { system }) => { ... });
    
    fromObservable(({ system }) => { ... });
    
    fromEventObservable(({ system }) => { ... });
    
    fromCallback((sendBack, receive, { system }) => { ... });
    
  • d3d6149c7: Typings for Typestate have been removed. The reason for this is that types for typestates needed to be manually specified, which is unsound because it is possible to specify impossible typestates; i.e., typings for a state's value and context that are impossible to achieve.

  • d3d6149c7: The actor.onDone(...) method is removed. Use actor.subscribe({ complete() {... } }) instead.

    - actor.onDone(() => { ... })
    + actor.subscribe({
    +  complete() {
    +    // ...
    +  }
    +})
    
  • d3d6149c7: The createModel() function has been removed in favor of relying on strong types in the machine configuration.

  • d3d6149c7: sync option has been removed from invoke and spawn.

  • d3d6149c7: Removed State['event'].

  • d3d6149c7: The final output of a state machine is now specified directly in the output property of the machine config:

    const machine = createMachine({
      initial: 'started',
      states: {
        started: {
          // ...
        },
        finished: {
          type: 'final'
          // moved to the top level
          //
          // output: {
          //   status: 200
          // }
        }
      },
      // This will be the final output of the machine
      // present on `snapshot.output` and in the done events received by the parent
      // when the machine reaches the top-level final state ("finished")
      output: {
        status: 200
      }
    });
    
  • d3d6149c7: Invoked/spawned actors are no longer available on service.children - they can only be accessed from state.children.

  • d3d6149c7: Removed mapState utility function.

  • d3d6149c7: The interpret(...) function has been deprecated and renamed to createActor(...):

    -import { interpret } from 'xstate';
    +import { createActor } from 'xstate';
    
    -const actor = interpret(machine);
    +const actor = createActor(machine);
    
  • d3d6149c7: Prefix wildcard event descriptors are now supported. These are event descriptors ending with ".*" which will match all events that start with the prefix (the partial event type before ".*"):

    // ...
    on: {
      'mouse.click': {/* ... */},
      // Matches events such as:
      // "pointer.move"
      // "pointer.move.out"
      // "pointer"
      'pointer.*': {/* ... */}
    }
    // ...
    

    Note: wildcards are only valid as the entire event type ("*") or at the end of an event type, preceded by a period (".*"):

    • "*"
    • "event.*"
    • "event.something.*"
    • "event.*.something"
    • "event*"
    • "event*.some*thing"
    • "*.something"
  • d3d6149c7: The interface for guard objects has changed. Notably, all guard parameters should be placed in the params property of the guard object:

    Example taken from Custom Guards:

    -cond: {
    +guard: {
    - name: 'searchValid', // `name` property no longer used
      type: 'searchValid',
    - minQueryLength: 3
    + params: {
    +   minQueryLength: 3
    + }
    }
    
  • d3d6149c7: All transitions became internal by default. The style of the target pattern (.child, sibling, #id) has now no effect on the transition type.

    Internal transitions don't reenter their source state when the target lies within it. You can still create external transitions (ones that reenter the source state under the mentioned circumstances) by explicitly setting external: true on the given transition.

  • d3d6149c7: exit actions of all states are no longer called when the machine gets stopped externally. Note that they are still called when the machine reaches its final state.

  • d3d6149c7: Machine#transition no longer handles invalid state values such as values containing non-existent state regions. If you rehydrate your machines and change machine's schema then you should migrate your data accordingly on your own.

  • d3d6149c7: Removed support for service.send(type, payload). We are using send API at multiple places and this was the only one supporting this shape of parameters. Additionally, it had not strict TS types and using it was unsafe (type-wise).

  • d3d6149c7: Spawned actors that have a referenced source (not inline) can be deeply persisted and restored:

    const machine = createMachine({
      context: ({ spawn }) => ({
        // This will be persisted
        ref: spawn('reducer', { id: 'child' })
    
        // This cannot be persisted:
        // ref: spawn(fromTransition((s) => s, { count: 42 }), { id: 'child' })
      })
    }).provide({
      actors: {
        reducer: fromTransition((s) => s, { count: 42 })
      }
    });
    
  • d3d6149c7: Removed State['actions']. Actions are considered to be a side-effect of a transition, things that happen in the moment and are not meant to be persisted beyond that.

Minor Changes

  • d3d6149c7: 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.

  • d3d6149c7: Merge sendBack and receive with other properties of fromCallback logic creator.

    const callbackLogic = fromCallback(({ input, system, self, sendBack, receive }) => { ... });
    
  • d3d6149c7: The state option of createActor(...) has been renamed to snapshot:

    createActor(machine, {
    - state: someState
    + snapshot: someState
    })
    

    Likewise, the .getPersistedState() method has been renamed to .getPersistedSnapshot():

    -actor.getPersistedState()
    +actor.getPersistedSnapshot()
    
  • d3d6149c7: spawn can now benefit from the actor types. Its arguments are strongly-typed based on them.

  • d3d6149c7: Significant improvements to error handling have been made:

    • Actors will no longer crash when an error is thrown in an observer (actor.subscribe(observer)).
    • Errors will be handled by observer's .error() handler:
      actor.subscribe({
        error: (error) => {
          // handle error
        }
      });
      
    • If an observer does not have an error handler, the error will be thrown in a clear stack so bug tracking services can collect it.
  • d3d6149c7: You can now spawnChild(...) actors directly outside of assign(...) action creators:

    import { createMachine, spawnChild } from 'xstate';
    
    const listenerMachine = createMachine({
      // ...
    });
    
    const parentMachine = createMachine({
      // ...
      on: {
        'listener.create': {
          entry: spawnChild(listenerMachine, { id: 'listener' })
        }
      }
      // ...
    });
    
    const actor = createActor(parentMachine).start();
    
    actor.send({ type: 'listener.create' });
    
    actor.getSnapshot().children.listener; // ActorRefFrom<typeof listenerMachine>
    
  • d3d6149c7: onSnapshot is now available for invoke configs. You can specify a transition there to be taken when a snapshot of an invoked actor gets updated. It works similarly to onDone/onError.

  • d3d6149c7: Partial event descriptors are now type-safe:

    createMachine({
      types: {} as {
        events:
          | { type: 'mouse.click.up'; direction: 'up' }
          | { type: 'mouse.click.down'; direction: 'down' }
          | { type: 'mouse.move' }
          | { type: 'keypress' };
      },
      on: {
        'mouse.click.*': {
          actions: ({ event }) => {
            event.type;
            // 'mouse.click.up' | 'mouse.click.down'
            event.direction;
            // 'up' | 'down'
          }
        },
        'mouse.*': {
          actions: ({ event }) => {
            event.type;
            // 'mouse.click.up' | 'mouse.click.down' | 'mouse.move'
          }
        }
      }
    });
    
  • d3d6149c7: State.from, StateMachine#createState and StateMachine#resolveStateValue were removed. They largely served the same purpose as StateMachine#resolveState and this is the method that is still available and can be used instead of them.

  • d3d6149c7: Params of actions and guards can now be resolved dynamically

    createMachine({
      types: {} as {
        actions:
          | { type: 'greet'; params: { surname: string } }
          | { type: 'poke' };
      },
      entry: {
        type: 'greet',
        params: ({ context }) => ({
          surname: 'Doe'
        })
      }
    });
    
  • d3d6149c7: Children IDs in combination with setup can now be typed using types.children:

    const machine = setup({
      types: {} as {
        children: {
          myId: 'actorKey';
        };
      },
      actors: {
        actorKey: child
      }
    }).createMachine({});
    
    const actorRef = createActor(machine).start();
    
    actorRef.getSnapshot().children.myId; // ActorRefFrom<typeof child> | undefined
    
  • d3d6149c7: You can now specify guard types for machines:

    createMachine({
      types: {} as {
        guards:
          | {
              type: 'isGreaterThan';
              params: {
                count: number;
              };
            }
          | { type: 'plainGuard' };
      }
      // ...
    });
    
  • d3d6149c7: You can now define strict tags for machines:

    createMachine({
      types: {} as {
        tags: 'pending' | 'success' | 'error';
      }
      // ...
    });
    
  • d3d6149c7: The state.configuration property has been renamed to state.nodes.

    - state.configuration
    + state.nodes
    
  • d3d6149c7: The onSnapshot: { ... } transition object is now supported for invoked machines, observables, promises, and transition functions:

    const machine = createMachine({
      // ...
      invoke: [
        {
          src: createMachine({ ... }),
          onSnapshot: {
            actions: (context, event) => {
              event.snapshot; // machine state
            }
          }
        },
        {
          src: fromObservable(() => ...),
          onSnapshot: {
            actions: (context, event) => {
              event.snapshot; // observable value
            }
          }
        },
        {
          src: fromTransition((state, event) => { ... }, /* ... */),
          onSnapshot: {
            actions: (context, event) => {
              event.snapshot; // transition function return value
            }
          }
        }
      ]
    });
    
  • d3d6149c7: The stop(...) action creator is renamed to stopChild(...), to make it clear that only child actors may be stopped from the parent actor.

  • d3d6149c7: Action parameters can now be directly accessed from the 2nd argument of the action implementation:

    const machine = createMachine(
      {
        // ...
        entry: {
          type: 'greet',
          params: { message: 'hello' }
        }
      },
      {
        actions: {
          greet: (_, params) => {
            params.message; // 'hello'
          }
        }
      }
    );
    
  • d3d6149c7: Input types can now be specified for machines:

    const emailMachine = createMachine({
      types: {} as {
        input: {
          subject: string;
          message: string;
        };
      },
      context: ({ input }) => ({
        // Strongly-typed input!
        emailSubject: input.subject,
        emailBody: input.message.trim()
      })
    });
    
    const emailActor = interpret(emailMachine, {
      input: {
        // Strongly-typed input!
        subject: 'Hello, world!',
        message: 'This is a test.'
      }
    }).start();
    
  • d3d6149c7: xstate.done.state.* events will now be generated recursively for all parallel states on the ancestors path.

  • d3d6149c7: Actor logic creators now have access to self:

    const promiseLogic = fromPromise(({ self }) => { ... });
    
    const observableLogic = fromObservable(({ self }) => { ... });
    
    const callbackLogic = fromCallback((sendBack, receive, { self }) => { ... });
    
    const transitionLogic = fromTransition((state, event, { self }) => { ... }, ...);
    
  • d3d6149c7: Guard parameters can now be directly accessed from the 2nd argument of the guard implementation:

    const machine = createMachine(
      {
        // ...
        on: {
          EVENT: {
            guard: {
              type: 'isGreaterThan',
              params: { value: 10 }
            }
          }
        }
      },
      {
        guards: {
          isGreaterThan: (_, params) => {
            params.value; // 10
          }
        }
      }
    );
    
  • d3d6149c7: You can now inspect actor system updates using the inspect option in createActor(logic, { inspect }). The types of inspection events you can observe include:

    • @xstate.actor - An actor ref has been created in the system
    • @xstate.event - An event was sent from a source actor ref to a target actor ref in the system
    • @xstate.snapshot - An actor ref emitted a snapshot due to a received event
    import { createMachine } from 'xstate';
    
    const machine = createMachine({
      // ...
    });
    
    const actor = createActor(machine, {
      inspect: (inspectionEvent) => {
        if (inspectionEvent.type === '@xstate.actor') {
          console.log(inspectionEvent.actorRef);
        }
    
        if (inspectionEvent.type === '@xstate.event') {
          console.log(inspectionEvent.sourceRef);
          console.log(inspectionEvent.targetRef);
          console.log(inspectionEvent.event);
        }
    
        if (inspectionEvent.type === '@xstate.snapshot') {
          console.log(inspectionEvent.actorRef);
          console.log(inspectionEvent.event);
          console.log(inspectionEvent.snapshot);
        }
      }
    });
    
  • d3d6149c7: Added support for expressions to cancel action.

  • d3d6149c7: The new enqueueActions(...) action creator can now be used to enqueue actions to be executed. This is a helpful alternative to the pure(...) and choose(...) action creators.

    const machine = createMachine({
      // ...
      entry: enqueueActions(({ context, event, enqueue, check }) => {
        // assign action
        enqueue.assign({
          count: context.count + 1
        });
    
        // Conditional actions (replaces choose(...))
        if (event.someOption) {
          enqueue.sendTo('someActor', { type: 'blah', thing: context.thing });
    
          // other actions
          enqueue('namedAction');
          // with params
          enqueue({ type: 'greet', params: { message: 'hello' } });
        } else {
          // inline
          enqueue(() => console.log('hello'));
    
          // even built-in actions
        }
    
        // Use check(...) to conditionally enqueue actions based on a guard
        if (check({ type: 'someGuard' })) {
          // ...
        }
    
        // no return
      })
    });
    
  • d3d6149c7: The default timeout for waitFor(...) is now Infinity instead of 10 seconds.

  • d3d6149c7: You can now specify action types for machines:

    createMachine({
      types: {} as {
        actions: { type: 'greet'; params: { name: string } };
      },
      entry: [
        {
          type: 'greet',
          params: {
            name: 'David'
          }
        },
        // @ts-expect-error
        { type: 'greet' },
        // @ts-expect-error
        { type: 'unknownAction' }
      ]
      // ...
    });
    
  • d3d6149c7: The state.meta getter has been replaced with state.getMeta() methods:

    - state.meta
    + state.getMeta()
    
  • d3d6149c7: Output types can now be specified in the machine:

    const machine = createMachine({
      types: {} as {
        output: {
          result: 'pass' | 'fail';
          score: number;
        };
      }
      // ...
    });
    
    const actor = createActor(machine);
    
    // ...
    
    const snapshot = actor.getSnapshot();
    
    if (snapshot.output) {
      snapshot.output.result;
      // strongly typed as 'pass' | 'fail'
      snapshot.output.score;
      // strongly typed as number
    }
    
  • d3d6149c7: Added interop observable symbols to ActorRef so that actor refs are compatible with libraries like RxJS.

  • d3d6149c7: You can now use the setup({ ... }).createMachine({ ... }) function to setup implementations for actors, actions, guards, and delays that will be used in the created machine:

    import { setup, createMachine } from 'xstate';
    
    const fetchUser = fromPromise(async ({ input }) => {
      const response = await fetch(`/user/${input.id}`);
      const user = await response.json();
      return user;
    });
    
    const machine = setup({
      actors: {
        fetchUser
      },
      actions: {
        clearUser: assign({ user: undefined })
      },
      guards: {
        isUserAdmin: (_, params) => params.user.role === 'admin'
      }
    }).createMachine({
      // ...
      invoke: {
        // Strongly typed!
        src: 'fetchUser',
        input: ({ context }) => ({ id: context.userId }),
        onDone: {
          guard: {
            type: 'isUserAdmin',
            params: ({ context }) => ({ user: context.user })
          },
          target: 'success',
          actions: assign({ user: ({ event }) => event.output })
        },
        onError: {
          target: 'failure',
          actions: 'clearUser'
        }
      }
    });
    
  • d3d6149c7: You can now specify delay types for machines:

    createMachine({
      types: {} as {
        delays: 'one second' | 'one minute';
      }
      // ...
    });
    
  • d3d6149c7: The event type of internal after events changed from xstate.after(1000)#some.state.id to xstate.after.1000.some.state.id for consistency.

  • d3d6149c7: pr: #4498
    author: @Andarist
    author: @davidkpiano

    State values and snapshot.matches() argument are now strongly-typed when using the setup API.

Patch Changes

  • d3d6149c7: The machine.options property has been renamed to machine.implementations

  • d3d6149c7: Fixed an issue with inline actions not being correctly executed when there was an equally named action provided through the implementations argument.

  • d3d6149c7: Fixed a runtime crash related to machines with their root state's type being final (createMachine({ type: 'final' })).

  • d3d6149c7: Added support to stateIn guard for checking a combination of an ID and a path, eg. stateIn('#b.B1').

  • d3d6149c7: Remove State['changed']. A new instance of State is being created if there are matching transitions for the received event. If there are no matching transitions then the current state is being returned.

  • d3d6149c7: Removed the ability to configure transitions using arrays:

    createMachine({
      on: [{ event: 'FOO', target: '#id' }]
      // ...
    });
    

    Only regular object-based configs will be supported from now on:

    createMachine({
      on: {
        FOO: '#id'
      }
      // ...
    });
    
  • d3d6149c7: Fixed an issue with actors not being reinstantiated correctly when an actor with the same ID was first stopped and then invoked/spawned again in the same microstep.

xstate - @xstate/[email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #3947 5fa3a0c74 Thanks @davidkpiano! - Removed the ability to pass a factory function as argument to useMachine.

  • #4006 42df9a536 Thanks @davidkpiano! - useActorRef is introduced, which returns an ActorRef from actor logic:

    const actorRef = useActorRef(machine, { ... });
    const anotherActorRef = useActorRef(fromPromise(...));
    

    useMachine is deprecated in favor of useActor, which works with machines and any other kind of logic

    -const [state, send] = useMachine(machine);
    +const [state, send] = useActor(machine);
    const [state, send] = useActor(fromTransition(...));
    

    useSpawn is removed in favor of useActorRef

    -const actorRef = useSpawn(machine);
    +const actorRef = useActorRef(machine);
    
    The previous use of `useActor(actorRef)` is now replaced with just using the `actorRef` directly, and with `useSelector`:
    
    ```diff
    -const [state, send] = useActor(actorRef);
    +const state = useSelector(actorRef, s => s);
    // actorRef.send(...)
    
  • #4050 fc88dc8e6 Thanks @davidkpiano! - The options prop has been added (back) to the Context.Provider component returned from createActorContext:

    const SomeContext = createActorContext(someMachine);
    
    // ...
    
    <SomeContext.Provider options={{ input: 42 }}>
      {/* ... */}
    </SomeContext.Provider>;
    
  • #4006 42df9a536 Thanks @davidkpiano! - useActor has been removed from the created actor context, you should be able to replace its usage with MyCtx.useSelector and MyCtx.useActorRef.

  • #4265 1153b3f9a Thanks @davidkpiano! - FSM-related functions have been removed.

  • #3947 5fa3a0c74 Thanks @davidkpiano! - Implementations for machines on useMachine hooks should go directly on the machine via machine.provide(...), and are no longer allowed to be passed in as options.

    -const [state, send] = useMachine(machine, {
    -  actions: {
    -    // ...
    -  }
    -});
    +const [state, send] = useMachine(machine.provide({
    +  actions: {
    +    // ...
    +  }
    +}));
    
  • #3148 7a68cbb61 Thanks @davidkpiano! - Removed getSnapshot parameter from hooks. It is expected that the received actorRef has to have a getSnapshot method on it that can be used internally.

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.
  • #4240 409552cf8 Thanks @davidkpiano! - The useMachine function is an alias of useActor.
  • #4436 340aee643 Thanks @Andarist! - Fast refresh now works as expected for most use-cases.
  • #4050 fc88dc8e6 Thanks @davidkpiano! - The observerOrListener argument has been removed from the 3rd argument of createActorContext(logic, options).
xstate - @xstate/[email protected]

Published by github-actions[bot] 11 months ago

Changelog

4.0.0-beta.11

Minor Changes

4.0.0-beta.10

Minor Changes

4.0.0-beta.9

Major Changes

4.0.0-beta.8

Patch Changes

4.0.0-beta.7

Major Changes

  • #4050 fc88dc8e6 Thanks @davidkpiano! - The options prop has been added (back) to the Context.Provider component returned from createActorContext:

    const SomeContext = createActorContext(someMachine);
    
    // ...
    
    <SomeContext.Provider options={{ input: 42 }}>
      {/* ... */}
    </SomeContext.Provider>;
    

Minor Changes

  • #4050 fc88dc8e6 Thanks @davidkpiano! - The observerOrListener argument has been removed from the 3rd argument of createActorContext(logic, options).

4.0.0-beta.6

Major Changes

4.0.0-beta.5

Patch Changes

  • #4033 9cb7cb51a Thanks @Andarist! - Fixed generated TS declaration files to not include .ts extensions in the import/export statements.

4.0.0-beta.4

Major Changes

  • #3947 5fa3a0c74 Thanks @davidkpiano! - Removed the ability to pass a factory function as argument to useMachine and useInterpret.

  • #4006 42df9a536 Thanks @davidkpiano! - useActorRef is introduced, which returns an ActorRef from actor logic:

    const actorRef = useActorRef(machine, { ... });
    const anotherActorRef = useActorRef(fromPromise(...));
    

    useMachine is deprecated in favor of useActor, which works with machines and any other kind of logic

    -const [state, send] = useMachine(machine);
    +const [state, send] = useActor(machine);
    const [state, send] = useActor(fromTransition(...));
    

    useSpawn is removed in favor of useActorRef

    -const actorRef = useSpawn(machine);
    +const actorRef = useActorRef(machine);
    
    The previous use of `useActor(actorRef)` is now replaced with just using the `actorRef` directly, and with `useSelector`:
    
    ```diff
    -const [state, send] = useActor(actorRef);
    +const state = useSelector(actorRef, s => s);
    // actorRef.send(...)
    
  • #4006 42df9a536 Thanks @davidkpiano! - useActor has been removed from the created actor context, you should be able to replace its usage with MyCtx.useSelector and MyCtx.useActorRef.

  • #3947 5fa3a0c74 Thanks @davidkpiano! - Implementations for machines on useMachine and useInterpret hooks should go directly on the machine via machine.provide(...), and are no longer allowed to be passed in as options.

    -const [state, send] = useMachine(machine, {
    -  actions: {
    -    // ...
    -  }
    -});
    +const [state, send] = useMachine(machine.provide({
    +  actions: {
    +    // ...
    +  }
    +}));
    

    @xstate/react will detect that the machine's config is still the same, and will not produce the "machine has changed" warning.

3.2.2

Patch Changes

  • #3919 6665f0a32 Thanks @c-w! - Updated the allowed range for the use-isomorphic-layout-effect dependency.

4.0.0-beta.3

4.0.0-alpha.2

Patch Changes

  • #3944 305a89001 Thanks @Andarist! - Releasing adjusted internals to make the alpha version of this module compatible with the current version of xstate@alpha

3.2.1

Patch Changes

  • #3829 c110c429d Thanks @Andarist! - Fixed compatibility of the generated TS types for createActorContext with pre-4.7.

3.2.0

Minor Changes

  • #3814 494203b3d Thanks @Andarist! - The Provider from createActorContext(...) now accepts the options={{...}} prop that takes the same object as the second argument to the useMachine(machine, options) hook.

    These options are no longer passed as the second argument to the createActorContext(machine) function:

    
    -const SomeContext = createActorContext(someMachine,
    -  { actions: { ... } });
    +const SomeContext = createActorContext(someMachine);
    
    // ...
    
    -<SomeContext.Provider>
    +<SomeContext.Provider options={{ actions: { ... } }}>
    
    // ...
    

3.1.2

Patch Changes

  • #3804 b53856d28 Thanks @farskid! - Interpreter options can now be specified in the second argument of createActorContext(machine, options).

3.1.1

Patch Changes

  • #3799 51d254692 Thanks @Andarist! - Fixed an issue that caused the internally used useSyncExternalStore to warn about the computed snapshot not being cached when a not-started machine service was passed to useActor.

3.1.0

Minor Changes

  • #3778 f12248b23 Thanks @davidkpiano! - The createActorContext(...) helper has been introduced to make global actors easier to use with React. It outputs a React Context object with the following properties:

    • .Provider - The React Context provider
    • .useActor(...) - A hook that can be used to get the current state and send events to the actor
    • .useSelector(...) - A hook that can be used to select some derived state from the actor's state
    • .useActorRef() - A hook that can be used to get a reference to the actor that can be passed to other components

    Usage:

    import { createActorContext } from '@xstate/react';
    import { someMachine } from './someMachine';
    
    // Create a React Context object that will interpret the machine
    const SomeContext = createActorContext(someMachine);
    
    function SomeComponent() {
      // Get the current state and `send` function
      const [state, send] = SomeContext.useActor();
    
      // Or select some derived state
      const someValue = SomeContext.useSelector((state) => state.context.someValue);
    
      // Or get a reference to the actor
      const actorRef = SomeContext.useActorRef();
    
      return (/* ... */);
    }
    
    function App() {
      return (
        <SomeContext.Provider>
          <SomeComponent />
        </SomeContext.Provider>
      );
    }
    

3.0.2

Patch Changes

  • #3752 4190c3fd6 Thanks @davidkpiano! - Computing the initial state is now consistent with useMachine and useActor, avoiding stale initial state problems with nested machines

4.0.0-alpha.1

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

3.0.1

Patch Changes

  • #3456 131d429ab Thanks @davidkpiano! - Add shallowEqual helper comparator function.

  • #3500 0dfc6d92f Thanks @Andarist! - Fixed an issue with useSelector always computing fresh snapshots internally for uninitialized services. This avoids the internal useSyncExternalStore from warning about the snapshot value not being cached properly.

4.0.0-alpha.0

Major Changes

  • #3148 7a68cbb61 Thanks @davidkpiano! - Removed getSnapshot parameter from hooks. It is expected that the received actorRef has to have a getSnapshot method on it that can be used internally.

Patch Changes

3.0.0

Major Changes

  • #2939 360e85462 Thanks @Andarist! - This package now accepts React 18 as a peer dep and the implementation has been rewritten to use use-sync-external-store package. This doesn't break compatibility with older versions of React since we are using the shim to keep compatibility with those older versions.

  • #2939 360e85462 Thanks @Andarist! - asEffect and asLayoutEffect action creators were removed. They were not fitting the React model that well and could lead to issues as their existence suggested that they are easy to use.

    To execute actions at those exact times you can always either just call your stuff directly from those effects or send events to the machine from those effects and execute explicit actions in response to said events.

  • #2939 360e85462 Thanks @Andarist! - The signatures of useMachine and useService integrating with @xstate/fsm were changed. They now only accept a single generic each (TMachine and TService respectively). This has been done to match their signatures with the related hooks that integrate with xstate itself.

Patch Changes

  • #2939 360e85462 Thanks @Andarist! - In v2 we have changed signatures of useMachine and useInterpret. Instead of accepting a list of generics they now only support a single generic: TMachine. This change, erroneously, was only introduced to types targeting [email protected] but the types targeting previous TS releases were still using the older signatures. This has now been fixed and users of older TS versions should now be able to leverage typegen with @xstate/react.

  • #2939 360e85462 Thanks @Andarist! - useMachine for xstate now correctly rerenders with the initial state when the internal service is being restarted. This might happen during Fast Refresh and now you shouldn't be able to observe this stale state that didn't match the actual state of the service.

  • #2939 360e85462 Thanks @Andarist! - useMachine for @xstate/fsm now starts the service in an effect. This avoids side-effects in render and improves the compatibility with StrictMode.

  • #2939 360e85462 Thanks @Andarist! - Implementations given to useMachine targeting @xstate/fsm are now updated in a layout effect. This avoid some stale closure problems for actions that are executed in response to events sent from layout effects.

  • Updated dependencies [360e85462, 360e85462]:

2.0.1

Patch Changes

  • #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).

2.0.0

Major Changes

  • #2674 e5a8b8dff Thanks @Andarist, @mattpocock! - To avoid breaking any consumers and to leverage the newly introduced typegen support, the major version of this package had to be bumped. While you can still use it with older versions of TS, the typegen support in this package requires TS version 4.0 or greater.

    When using hooks from @xstate/react it's recommended to skip providing explicit generics to them. Note that that generics list has changed since v1 and we now only accept a single generic, TMachine.

  • #2674 ab919d300 Thanks @Andarist! - Removed already deprecated useService from @xstate/react. You can replace its usage with useActor.

Patch Changes

  • #2957 8550ddda7 Thanks @davidkpiano! - The repository links have been updated from github.com/davidkpiano to github.com/statelyai.

1.6.3

Patch Changes

1.6.2

Patch Changes

  • #2736 2246ae051 Thanks @Andarist, @davidkpiano, @VanTanev! - The useSelector(...) hook now works as expected when the actor passed in changes. The hook will properly subscribe to the new actor and select the desired value. See #2702

  • #2685 469268d39 Thanks @farskid, @Andarist! - Fixed a regression with a development-only warning not being shown when a machine reference is updated during the hook lifecycle. This usually happens when machine options are dependent on external values and they're passed via withConfig.

    const machine = createMachine({
      initial: 'foo',
      context: { id: 1 },
      states: {
        foo: {
          on: {
            CHECK: {
              target: 'bar',
              cond: 'hasOverflown'
            }
          }
        },
        bar: {}
      }
    });
    
    const [id, setId] = useState(1);
    const [current, send] = useMachine(
      machine.withConfig({
        guards: {
          hasOverflown: () => id > 1 // id is a reference to an outside value
        }
      })
    );
    
    // later when id updates
    setId(2);
    // Now the reference passed to `useMachine` (the result of `machine.withConfig`) is updated but the interpreted machine stays the same. So the guard is still the previous one that got passed to the `useMachine` initially, and it closes over the stale `id`.
    

1.6.1

Patch Changes

  • #2587 5aaa8445c Thanks @Andarist! - Fixed an issue with implementations provided outside of React being wiped out and unusable.

1.6.0

Minor Changes

  • 4b4872ca #2241 Thanks @mattpocock! - Changed the behaviour of guards, delays and activities when declared as options in useMachine/useInterpret.

    Previously, guards could not reference external props, because they would not be updated when the props changed. For instance:

    const Modal = (props) => {
      useMachine(modalMachine, {
        guards: {
          isModalOpen: () => props.isOpen
        }
      });
    };
    

    When the component is created, props.isOpen would be checked and evaluated to the initial value. But if the guard is evaluated at any other time, it will not respond to the props' changed value.

    This is not true of actions/services. This will work as expected:

    const Modal = (props) => {
      useMachine(modalMachine, {
        actions: {
          consoleLogModalOpen: () => {
            console.log(props.isOpen);
          }
        }
      });
    };
    

    This change brings guards and delays into line with actions and services.

    ⚠️ NOTE: Whenever possible, use data from within context rather than external data in your guards and delays.

Patch Changes

  • fe3e859f #2522 Thanks @farskid, @Andarist! - Fixed an issue with actors not being spawned correctly by useMachine and useInterpret when they were defined a lazily evaluated context, like for example here:

    createMachine({
      // lazy context
      context: () => ({
        ref: spawn(() => {})
      })
    });
    

1.5.1

Patch Changes

  • 453acacb #2389 Thanks @davidkpiano! - An internal issue where the spawnBehavior import for the useSpawn(...) hook was broken internally has been fixed.

1.5.0

Minor Changes

  • 432b60f7 #2280 Thanks @davidkpiano! - Just like useInvoke(...), other types of actors can now be spawned from behaviors using useSpawn(...):

    import { fromReducer } from 'xstate/lib/behaviors';
    import { useActor, useSpawn } from '@xstate/react';
    
    type CountEvent = { type: 'INC' } | { type: 'DEC' };
    
    const countBehavior = fromReducer(
      (count: number, event: CountEvent): number => {
        if (event.type === 'INC') {
          return count + 1;
        } else if (event.type === 'DEC') {
          return count - 1;
        }
    
        return count;
      },
      0 // initial state
    );
    
    const countMachine = createMachine({
      invoke: {
        id: 'count',
        src: () => fromReducer(countReducer, 0)
      },
      on: {
        INC: {
          actions: forwardTo('count')
        },
        DEC: {
          actions: forwardTo('count')
        }
      }
    });
    
    const Component = () => {
      const countActorRef = useSpawn(countBehavior);
      const [count, send] = useActor(countActorRef);
    
      return (
        <div>
          Count: {count}
          <button onClick={() => send({ type: 'INC' })}>Increment</button>
          <button onClick={() => send({ type: 'DEC' })}>Decrement</button>
        </div>
      );
    };
    

1.4.0

Minor Changes

  • 849ec56c #2286 Thanks @davidkpiano! - The useService(...) hook will be deprecated, since services are also actors. In future versions, the useActor(...) hook should be used instead:

    -const [state, send] = useService(service);
    +const [state, send] = useActor(service);
    

Patch Changes

  • ea3aaffb #2326 Thanks @davidkpiano! - The send type returned in the tuple from useActor(someService) was an incorrect never type; this has been fixed.

1.3.4

Patch Changes

  • aa3c2991 #2223 Thanks @davidkpiano! - Support for actor refs with the .getSnapshot() method (added for spawned actors in XState version 4.19) is now supported in the useActor(...) hook.

1.3.3

Patch Changes

  • 27e7242c #2112 Thanks @davidkpiano! - The executeEffect function is no longer exported (was meant to be internal and is useless as a public function anyway). This also fixes a circular dependency issue.

1.3.2

Patch Changes

  • bb5e81ea #2050 Thanks @theKashey! - Added an explicit entrypoint for @xstate/react/fsm which you can use instead of @xstate/react/lib/fsm. This is the only specifier that will be supported in the future - the other one will be dropped in the next major version.

    -import { useMachine } from '@xstate/react/lib/fsm'
    +import { useMachine } from '@xstate/react/fsm'
    

1.3.1

Patch Changes

  • b076b253 #1947 Thanks @lukekarrys! - Fix typing of the service returned from the fsm useMachine hook by passing it Typestate

  • 9b5dc784 #1950 Thanks @Andarist! - Fixed an issue with toObserver being internally imported from xstate/lib/utils which has broken UMD build and the declared peer dep contract.

1.3.0

Minor Changes

  • 577ae023 #1915 Thanks @davidkpiano! - New hook: useInterpret(machine), which is a low-level hook that interprets the machine and returns the service:

    import { useInterpret } from '@xstate/react';
    import { someMachine } from '../path/to/someMachine';
    
    const App = () => {
      const service = useInterpret(someMachine);
    
      // ...
    };
    
  • 577ae023 #1915 Thanks @davidkpiano! - New hook: useSelector(actor, selector), which subscribes to actor and returns the selected state derived from selector(snapshot):

    import { useSelector } from '@xstate/react';
    
    const App = ({ someActor }) => {
      const count = useSelector(someActor, (state) => state.context.count);
    
      // ...
    };
    

1.2.2

Patch Changes

  • 4b31cefb #1780 Thanks @Andarist! - Fixed an issue with some external packages not being bundled correctly into the UMD bundles.

1.2.1

Patch Changes

1.2.0

Minor Changes

1.1.0

Minor Changes

  • 89f9c27c #1622 Thanks @davidkpiano! - Spawned/invoked actors and interpreters are now typed as extending ActorRef rather than Actor or Interpreter. This unification of types should make it more straightforward to provide actor types in React:

    import { ActorRef } from 'xstate';
    import { useActor } from '@xstate/react';
    
    const Child: React.FC<{ actorRef: ActorRef<SomeEvent, SomeEmitted> }> = ({
      actorRef
    }) => {
      // `state` is typed as `SomeEmitted`
      // `send` can be called with `SomeEvent` values
      const [state, send] = useActor(actorRef);
    
      // . ..
    };
    

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

    import { createMachine, ActorRefFrom } from 'xstate';
    import { useActor } from '@xstate/react';
    
    const someMachine = createMachine<SomeContext, SomeEvent>({
      // ...
    });
    
    const Child: React.FC<{ someRef: ActorRefFrom<typeof someMachine> }> = ({
      someRef
    }) => {
      // `state` is typed as `State<SomeContext, SomeEvent>`
      // `send` can be called with `SomeEvent` values
      const [state, send] = useActor(someRef);
    
      // . ..
    };
    

1.0.3

Patch Changes

1.0.2

Patch Changes

  • c7927083 #1516 Thanks @davidkpiano! - The send function returned from the useService() now can take two arguments (an event type and payload), to match the behavior of @xstate/react version 0.x.

  • db77623a #1516 Thanks @davidkpiano! - The send value returned from the useService() hook will now accept a payload, which matches the signature of the send value returned from the useMachine() hook:

    const [state, send] = useService(someService);
    
    // ...
    
    // this is OK:
    send('ADD', { value: 3 });
    
    // which is equivalent to:
    send({ type: 'ADD', value: 3 });
    
  • 93f6db02 #1594 Thanks @Andarist! - Fixed an issue with internal setState in useService being called with 2 arguments instead of 1.

  • 72b0880e #1504 Thanks @Andarist! - Fixed issue with useService returning an initial state for services in their final states.

1.0.1

Patch Changes

  • c0bd0407 #1493 Thanks @davidkpiano! - There will now be a descriptive error when trying to use an actor-like object in the useService() hook, where useActor() should be preferred:

    Attempted to use an actor-like object instead of a service in the useService() hook. Please use the useActor() hook instead.

All notable changes to this project will be documented in this file.

[1.0.0-rc.7]

  • The machine passed into useMachine(machine) can now be passed in lazily:

    const [state, send] = useMachine(() => createMachine(/* ... */));
    
    // ...
    

    This has the benefit of avoiding unnecessary machine initializations whenever the component rerenders.

  • The useActor hook now takes a second argument: getSnapshot which is a function that should return the last emitted value:

    const [state, send] = useActor(someActor, (actor) => actor.current);
    

[1.0.0-rc.6]

[1.0.0-rc.5]

  • You can now schedule actions in useEffect or useLayoutEffect via:
    • asEffect - queues the action to be executed in useEffect
    • asLayoutEffect - queues the action to be executed in useLayoutEffect
import { createMachine } from 'xstate';
import { useMachine, asEffect } from '@xstate/react';

const machine = createMachine({
  initial: 'focused',
  states: {
    focused: {
      entry: 'focus'
    }
  }
});

const Input = () => {
  const inputRef = useRef(null);
  const [state, send] = useMachine(machine, {
    actions: {
      focus: asEffect(() => {
        inputRef.current && inputRef.current.focus();
      })
    }
  });

  return <input ref={inputRef} />;
};

[0.8.1]

  • Services are now kept up to date

[0.8.0]

  • The useActor() hook is now available.
  • Support for persisted states

[0.7.1]

  • Actions passed into useMachine(..., { actions: { ... } }) will now be kept up-to-date and no longer reference stale data.

[0.7.0]

Added

  • Machine configuration can now be merged into the options argument of useMachine(machine, options). The following Machine Config options are available: guards, actions, activities, services, delays and updates (NOTE: context option is not implemented yet, use withContext or withConfig instead for the meantime)
const [current, send] = useMachine(someMachine, {
  actions: {
    doThing: doTheThing
  },
  services: {
    /* ... */
  },
  guards: {
    /* ... */
  }
  // ... etc.
});
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #4535 6a9fa1f11 Thanks @Andarist! - The escalate() action is removed. Just throw an error normally.

  • #4539 a2a377f47 Thanks @davidkpiano! - The error event (type: 'xstate.error.*') now has the error data on the event.error instead of event.data:

    // ...
    invoke: {
      src: 'someSrc',
      onError: {
        actions: ({ event }) => {
    -     event.data;
    +     event.error;
        }
      }
    }
    
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Minor Changes

  • #4533 2495aa21d Thanks @Andarist! - The state option of createActor(...) has been renamed to snapshot:

    createActor(machine, {
    - state: someState
    + snapshot: someState
    })
    

    Likewise, the .getPersistedState() method has been renamed to .getPersistedSnapshot():

    -actor.getPersistedState()
    +actor.getPersistedSnapshot()
    
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #4531 a5b198340 Thanks @Andarist! - The order of type parameters in ActorRef has been changed from from ActorRef<TEvent, TSnapshot> to ActorRef<TSnapshot, TEvent> for consistency with other types.

  • #4529 43843ea26 Thanks @Andarist! - The pure() and choose() action creators have been removed, in favor of the more flexible enqueueActions() action creator:

    entry: [
      // pure(() => {
      //   return [
      //     'action1',
      //     'action2'
      //   ]
      // }),
      enqueueActions(({ enqueue }) => {
        enqueue('action1');
        enqueue('action2');
      })
    ];
    
    entry: [
      // choose([
      //   {
      //     guard: 'someGuard',
      //     actions: ['action1', 'action2']
      //   }
      // ]),
      enqueueActions(({ enqueue, check }) => {
        if (check('someGuard')) {
          enqueue('action1');
          enqueue('action2');
        }
      })
    ];
    

Minor Changes

  • #4521 355e89627 Thanks @davidkpiano! - The event type of internal after events changed from xstate.after(1000)#some.state.id to xstate.after.1000.some.state.id for consistency.
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Minor Changes

  • #4429 7bcc62cbc Thanks @davidkpiano! - The new enqueueActions(...) action creator can now be used to enqueue actions to be executed. This is a helpful alternative to the pure(...) and choose(...) action creators.

    const machine = createMachine({
      // ...
      entry: enqueueActions(({ context, event, enqueue, check }) => {
        // assign action
        enqueue.assign({
          count: context.count + 1
        });
    
        // Conditional actions (replaces choose(...))
        if (event.someOption) {
          enqueue.sendTo('someActor', { type: 'blah', thing: context.thing });
    
          // other actions
          enqueue('namedAction');
          // with params
          enqueue({ type: 'greet', params: { message: 'hello' } });
        } else {
          // inline
          enqueue(() => console.log('hello'));
    
          // even built-in actions
        }
    
        // Use check(...) to conditionally enqueue actions based on a guard
        if (check({ type: 'someGuard' })) {
          // ...
        }
    
        // no return
      })
    });
    
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Major Changes

  • #4492 63d923857 Thanks @Andarist! - All errors caught while executing the actor should now consistently include the error in its snapshot.error and should be reported to the closest error listener.

Patch Changes

  • #4523 e21e3f959 Thanks @Andarist! - Fixed an issue with contextual parameters in input factories of input-less actors
xstate - [email protected]

Published by github-actions[bot] 11 months ago

Minor Changes

Patch Changes

  • #4516 daf532b2f Thanks @Andarist! - Export all TS snapshot types to fix type portability errors that could be reported when generating declaration files for files depending on xstate.