Skip to content

Releases: statelyai/xstate

v4.2.4

13 Jan 19:40
Compare
Choose a tag to compare
  • 🕵️ Anonymous functions (including arrow functions) as actions are no longer silently ignored, and are executed. #298
  • 💥 New method on services: .execute(state) will execute the state's actions (defined on state.actions). #295
  • 📖 Documentation updates:
  • 🕐 State history is correctly disposed now, to prevent memory leaks.
  • ☎️ Second argument to invoked callbacks, onEvent, now let you register event listeners for when the machine sends events directly to the invoked callbacks. (docs coming 🔜)
  • 🚦 Activities are now properly disposed when a service is stopped.

v4.2.3

28 Dec 05:11
Compare
Choose a tag to compare
  • 🔢 Fix for the order property when one isn't defined in the machine config for a given state node.
  • 🔧 Small type fix for the StateListener (it should accept an event object, not an event)
  • 📞 Type fix for the service config property to allow specifying promises and callbacks as services. #285

v4.2.2

22 Dec 03:39
Compare
Choose a tag to compare

v4.2.1

06 Dec 11:44
Compare
Choose a tag to compare
  • 📚 Lots of documentation updates: https://xstate.js.org/docs
  • 💥 Added the .event property to State instances, so you can know which event caused the transition to the current State:
const lightMachine = Machine({ /* ... */ });

const currentState = lightMachine.transition('green', 'TIMER');

console.log(currentState.event);
// => { type: 'TIMER' }
  • 👪 Fixed #269 by ensuring two things:
    • Services invoked on the parent machine (which are alive for the lifetime of the machine, FYI) are successfully invoked when the machine is started
    • Starting activities (such as invoke, which is an activity) should be executed before executing onEntry actions.

v4.2.0

04 Dec 01:34
Compare
Choose a tag to compare
  • ➕ Added CODE_OF_CONDUCT.md
  • 💙 Added Webflow as a sponsor
  • 📖 Added documentation directly to the master branch for easier maintenance (in the /docs directory)
  • The send(...) action creator now accepts an "event expression", which is a function that provides context and event as arguments and returns an Event:
import { actions } from 'xstate';
const { send } = actions;

// contrived example
const sendName = send((ctx, event) => ({
  type: 'NAME',
  name: ctx.user.name
}));

Only use this when necessary - a static argument, e.g., send({ type: 'FOO' }) is still preferred (because it's more deterministic). This follows the <send> SCXML spec for the eventexpr attribute.

This also works with sendParent(eventExpr).

  • 🆕 Added the state.inert getter property on State instances, which represents a state as-is without actions.
  • #️⃣ Targets can now be represented as getters that return references to StateNodes rather than string IDs:
const lightMachine = Machine({
  // ...
  states: {
	green: {
	  on: {
		get TIMER() {
		  return lightMachine.states.yellow;
		}
	  }
    },
	yellow: { /* ... */ }
  }
});

This is completely optional, and useful when if you want to avoid strings or have stricter typings.

  • 🔧 Strict machines (strict: true) will no longer throw errors for built-in events, such as done.state.* or done.invoke.* events.
  • 📞 Now you can invoke a service that can send multiple events back to the parent via a callback:
// ...
states: {
  counting: {
	invoke: {
	  src: (ctx, event) => (callback) => {
		const interval = setInterval(() => {
		  // sends 'SOME_EVENT' to parent on interval
		  callback('SOME_EVENT');
		}, ctx.interval);

		return () => clearInterval(interval);
	  }
	}
  }
}
// ...

This makes it easy to communicate with streams of events (such as Observables) through invoke. Note that since there can be an infinite number of events, it's not assumed that a callback interval will call onDone. Instead, you have to manually send doneInvoke(id) via the callback (this will be in the docs 🔜).

  • ⛔️ An interpreted machine (service) will now throw an error if the initial state given to it is invalid.

v4.1.1

18 Nov 14:18
Compare
Choose a tag to compare
  • .npmignore was removed ❌
  • The "files" property was added instead to ensure only the desired files show up in the NPM package.

v4.1.0

18 Nov 14:17
Compare
Choose a tag to compare
  • For browser builds, the global export was renamed to XState (previously: xstate).
  • The data property of the StateNode config is now an "Assigner" or "PropertyAssigner" instead of any.
  • The new State() constructor now takes an object with properties for each parameter, instead of positional arguments.
  • New static method: State.create({ ... }) which does the same as above.
  • The error(...) action creator now takes src as a second argument. For errors from an invoked service, the service ID will populate the src.
  • onError: ... transition property added to invoke config.
  • Numeric targets are now being coerced to strings, to resolve edge-case issues e.g., 3 !== '3' when selecting state transitions.
  • actionTypes.null is now actionTypes.nullEvent, which alleviates some autocomplete issues in VS Code, etc.
  • Instead of params and content, data will be used (polymorphic property - can take an "Assigner" or "PropertyAssigner")
  • Done data is now correctly passed to the 'done.state' event. #224
  • The process.env.NODE_ENV check will no longer produce errors in browser environments. #227
  • The Machine interface was renamed to StateMachine to prevent naming conflicts. #231
  • History state edge-cases are now resolved, and usage of the history property to indicate a history state is deprecated (use type: 'history' instead).

v4.0.1

18 Nov 14:05
Compare
Choose a tag to compare
  • TypeScript downgraded to 3.0.3 due to an existing bug in 3.1.3. See #213 for more information.

v4.0.0

30 Oct 00:48
Compare
Choose a tag to compare

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!

v3.3.3

14 Jul 00:49
Compare
Choose a tag to compare
  • A substantial refactor to the history state algorithm fixed a few outstanding history issues: #133, #137
  • New method: machine.getStateNodeByPath(['foo', 'bar', 'baz']) (documentation pending)
  • Better resolution of deep parallel states to fix failing parallel state tests. #140