github statelyai/xstate v4.0.0

5 years ago

State instance

  • 🆕 state.changed property indicates whether a state has changed from a previous state. A state is considered changed if:
    • its value is different from the previous state value
    • its value is unchanged but there are actions to be executed on the new state
  • 🆕 state.nextEvents property represents all next possible events from the current state.
  • 🆕 state.matches(parentStateValue) is equivalent to the matches() utility function, in that it determines if the current state matches the provided parent state value.
  • 💥 state.actions now returns an array of action objects instead of plain strings, with at least two properties:
    • type - the action type (string)
    • exec - the action implementation function (if it is defined).

Parallel states

  • 🔧 Flat nested parallel states are now representable in the state value, as empty objects ({}).
  • 💥 Using parallel: true is deprecated; use type: 'parallel' instead.

History States

  • 🆕 History states are explicitly defined with type: 'history' and history: 'parallel' or history: 'deep' (parallel by default). See https://xstate.js.org/docs/guides/history/ for more details.
  • 💥 The magic '$history' string is deprecated.

Final States

Machine

  • 🆕 machine.withConfig(...) lets you override configuration options in the original machine, as a new machine instance:
const customMachine = someMachine.withConfig({
  actions: {
	something: () => console.log('overridden something action')
  },
  guards: {
    someCondition: () => true
  }
});

Invoke

Interpreter

Assign and context

  • 🆕 Machines can be defined with a context, which is the extended state of the machine.
  • 🆕 The assign() action allows you to update the context declaratively. See https://xstate.js.org/docs/guides/context/ for details.
// increment a counter
{
  actions: assign({ counter: ctx => ctx.counter + 1 })
}

Delayed transitions and events

  • 🆕 Delayed transitions can be defined on the after: ... property of a state node config:
{
  after: {
	1000: 'yellow'
  }
}
  • 🆕 Delayed events can be defined as an option in send():
actions: send('ALERT', { delay: 1000 });

See https://xstate.js.org/docs/guides/delays/ for more details.

Actions and Events

  • 🆕 send(event, { to: ... }) allows you to specify the recipient machine for an event. See https://xstate.js.org/docs/guides/communication/#sending-events for more details.
  • 🆕 sendParent(event, options) similarly sends an event to the parent statechart that invoked the current child machine.
  • 🆕 log(expr, label) declaratively logs expressions given the current context and event.
  • 🆕 after(delay, id) creates an event for the given state node id that represents a delay of delay ms.
  • 🆕 done(id, data) represents that the parent state node with the given id is "done" - that is, all its final child state nodes have been reached. See https://xstate.js.org/docs/guides/final/ for more details.
  • 🆕 error(data) represents an execution error as an event.

Breaking changes

IDs are recommended on the root state node (machine):

const machine = Machine({
  id: 'light',
  initial: 'green',
  states: { /* ... */ }
});

This syntax will no longer work:

// ⚠️ won't work in v4
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: {
          yellow: { actions: ['doSomething'] }
        }
      }
    }
  }
});

You now specify the transition as an object (or an array of objects) instead:

// ✅ will work in v4
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: {
          target: 'yellow',
          actions: 'doSomething' // notice: array not necessary anymore!
        }
      }
    }
  }
});

Simple transitions as strings still work:

// ✅ still good
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    }
  }
});

When specifying types of state nodes, use type:

- parallel: true,
+ type: 'parallel'

- history: true,
+ type: 'history',
+ history: 'deep', // 'shallow' by default

And every property that takes an array now optionally takes an array if you have a single element:

{
  actions: ['doSomething']
  
  // You can do this instead:
  actions: 'doSomething'
}

Which is purely for convenience. That's about it!

Don't miss a new xstate release

NewReleases is sending notifications on new releases.