Skip to content

Releases: statelyai/xstate

xstate@4.7.5

28 Dec 22:06
be2bc0d
Compare
Choose a tag to compare

Patch Changes

  • 6b3d767: Fixed issue with delayed transitions scheduling a delayed event for each transition defined for a single delay.

@xstate/test@0.2.1

28 Dec 22:06
be2bc0d
Compare
Choose a tag to compare

Patch Changes

  • fc2a78c: Fix description text if no meta description is given
  • Updated dependencies [6b3d767]
    • xstate@4.7.5

v4.7.0

30 Nov 15:44
Compare
Choose a tag to compare
  • 🐌 If a subscriber/listener subscribes to a service that is already running, it will now receive the current state upon subscription. #814
  • 🆙 The new escalate() action creator escalates custom error events to a parent machine, which can catch those in the onError transition:
import { createMachine, actions } from 'xstate';
const { escalate } = actions;

const childMachine = createMachine({
  // ...
  // This will be sent to the parent machine that invokes this child
  entry: escalate({ message: 'This is some error' })
});

const parentMachine = createMachine({
  // ...
  invoke: {
    src: childMachine,
    onError: {
      actions: (context, event) => {
        console.log(event.data);
        //  {
        //    type: ...,
        //    data: {
        //      message: 'This is some error'
        //    }
        //  }
      }
    }
  }
});
  • ❓ You can now specify undefined as the first argument for machine.transition(...), which will default to the initial state:
lightMachine.transition(undefined, 'TIMER').value;
// => 'yellow'
  • 🤝 Services (invoked machines) are now fully subscribable and can interop with libraries that implement TC39 Observbles like RxJS. See https://xstate.js.org/docs/recipes/rxjs.html for more info.

  • 🆔 When a service is invoked, it has a uniquely generated sessionId, which corresponds to _sessionid in SCXML. This ID is now available directly on the state object to identify which service invocation the state came from: state._sessionid #523

  • ⚙️ The original config object passed to Machine(config) (or createMachine(config)) is now the exact same object reference in the resulting machine.config property.

  • 🎰 The createMachine() factory function now exists and is largely the same as Machine(), except with a couple differences:

    • The generic type signature is <TContext, TEvent, TState> instead of <TContext, TStateSchema, TEvent>.
    • There is no third argument for specifying an initial context. Use .withContext(...) on the machine or return a machine with the expected context in a factory instead.
  • 🛑 Event sent to a stopped service will no longer execute any actions, nor have any effect. #735

  • 🚸 The invoked actors are now directly available on state.children.

  • ✍️ Plain strings can now be logged in the log() action creator:

entry: log('entered here', 'some label')
const quietMachine = Machine({
  id: 'quiet',
  initial: 'idle',
  states: {
    idle: {
      on: {
        WHISPER: undefined,
        // On any event besides a WHISPER, transition to the 'disturbed' state
        '*': 'disturbed'
      }
    },
    disturbed: {}
  }
});

quietMachine.transition(quietMachine.initialState, 'WHISPER');
// => State { value: 'idle' }

quietMachine.transition(quietMachine.initialState, 'SOME_EVENT');
// => State { value: 'disturbed' }

v4.6.7

12 Jul 20:08
Compare
Choose a tag to compare
  • A regression in the new stateUtils.ts file caused a bundling failure; this is now fixed.

v4.6.6

12 Jul 20:08
Compare
Choose a tag to compare
  • A configuration change that included testing files as an included path in tsconfig.json was reverted.

v4.6.5

12 Jul 20:07
Compare
Choose a tag to compare

Improvements

  • The service.children property of interpreter instances is now marked as public.
  • An improved algorithm for determining state nodes on a transition fixed #518. This algorithm will eventually replace the current one, while maintaining the same behavior.
  • spawn() is now typed correctly. #521

v4.6.4

02 Jul 17:05
Compare
Choose a tag to compare

Features

  • 🔭 Actors that are synced (with { sync: true }) will have their updates reflected as state.changed === true.
  • 📻 This also goes for manual updates (via sendParent(actionTypes.update)), when actors are not synced.

Fixes

  • 🤦‍♂ @vuepress/plugin-google-analytics was accidentally specified as a dependency and not a devDependency. This is fixed. XState will always be dependency-free.

v4.6.3

26 Jun 17:22
Compare
Choose a tag to compare
  • 🔧 This PR contains a fix for #509, where services were converted to (more limited) actors. The limitation was unnecessary, and caused some bugs. Those bugs are now fixed without removing any functionality.

v4.6.2

19 Jun 12:52
Compare
Choose a tag to compare

Features

  • 🎭 Machines can now keep in sync with spawned child machines when setting spawn(..., { sync: true }) (false by default). This means that the parent machine will receive a special "xstate.update" action with the updated actor state and the actor ID:
// ...
actions: assign({
  myTodo: spawn(todoMachine, { sync: true }) // keeps myTodo.state in sync
})
// ...

This will keep sync with the referenced actor's state, by mutating the actorRef.state property with the new actor state. When doing change detection, do not rely on actorRef to change - that will always stay constant (unless reassigned). Instead, keep a previous reference to its state and compare it:

const state = currentState.context.myTodo.state;

// ... assume an "xstate.update" event was sent

const nextState = currentState.context.myTodo.state;

state === nextState;
// => false

⚠️ Also, be careful when using { sync: true } because an "xstate.update" event will occur for every single state transition in every spawned actor, which will make the service more "chatty". Always prefer explicit updates.

Fixes

  • ⚙️ machine.withContext() now internally uses original config, not machine definition. #491

@xstate/graph

  • 🤝 xstate is now a peer dependency.

@xstate/react

  • 🤖 You can now specify machine config directly in useMachine(...) options:
const [current, send] = useMachine(someMachine, {
  actions: {
    doThing: doTheThing
  },
  services: {/* ... */},
  guards: {/* ... */},
  // ... etc.
});

v4.6.0

28 May 21:57
Compare
Choose a tag to compare

Actors

  • 🎭 Support for actors (read up on the Actor model has landed! Actors are dynamic entities that can send and receive messages from each other, spawn other actors, and only modify their own local state. This is a great model for communicating state machines. Think of it like services but dynamic (same implementation!).

Read the docs 📖 on actors.

const todoMachine = Machine({
  id: 'todo',
  // ...
});


const todosMachine = Machine({
  id: 'todos',
  context: {
    todos: []
  },
  // ...
  on: {
    'TODOS.ADD': {
      actions: assign({
        todos: (ctx, e) => [
          ...ctx.todos,
          { data: e.data, ref: spawn(todoMachine) }
        ]
      }
    },
    'TODO.COMPLETE': {
      actions: send('COMPLETE', {
        to: (ctx, e) => ctx.todos
          .find(todo => todo.id === e.id)
          .ref
      })
    }
  }
});

Observables

  • 🔵 Observables can now be invoked (or spawned):
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';
// ...

const intervalMachine = Machine({
  id: 'interval',
  context: { value: 1000 },
  invoke: {
    src: (context) => interval(context.value)
      .pipe(map(n => ({ type: 'UPDATE', value: n })))
  },
  on: {
    UPDATE: { /* ... */ }
  }
  // ...
});
const service = interpret(machine).start();

// Subscribe to state transitions
const sub = service.subscribe(state => {
  console.log(state);
});

// Stop subscribing
sub.unsubscribe();
  • 💥 For executing custom actions, service.execute(...) can now be configured dynamically with custom action implementations:
const alertMachine = Machine({
  id: 'alert',
  context: { message: '' },
  states: { /* ... */ },
  on: {
    NOTIFY: { actions: 'alert' }
  }
});

const service = interpret(alertMachine, { execute: false })
  .onTransition(state => {
    // execute with custom action
    service.execute(state, {
      alert: (context) => AlertThing.showCustomAlert(context.message)
    });
  })
  .start();

service.send('NOTIFY');
// => shows custom alert
  • 🎯 The send() action creator now supports target expressions for its to option:
// string target 
send('SOME_EVENT', { to: (ctx, e) => e.id });

// reference target
send('SOME_EVENT', { to: (ctx, e) => ctx.someActorRef });
  • 💧 New action creator: pure() allows you to specify a function that returns one or more action objects. The only rule is that this action creator must be pure (hence the name) - no executed side-effects must occur in the function passed to pure():
// Assign and do some other action
actions: pure((ctx, e) => {
  return [
    assign({ foo: e.foo }),
    { type: 'anotherAction', value: ctx.bar }
  ];
});

Fixes and enhancements

  • ⚠️ Warnings are now only displayed when not in production mode.
  • 🚥 Services that are started will now not have any unintended side-effects if .start() is called again.
  • 🕳 Transitions for deep flat parallel state value now resolve as expected (thanks @Andarist for the test) #458 #465
  • ❗️ The service.state and service.initialState are now always defined on an Interpreter (service) instance, which should fix some tiny React hook issues.
  • 🔍 Destructuring { matches } from a State instance will no longer lose its this reference, thanks to @farskid #- #440
  • ❓ A service creator that returns undefined will no longer crash the service. #430 #431