Actor-based state management & orchestration for complex app logic.
MIT License
Bot releases are hidden (Show)
.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' }
invoke
, which is an activity) should be executed before executing onEntry
actions.master
branch for easier maintenance (in the /docs
directory)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)
.
state.inert
getter property on State
instances, which represents a state as-is without actions.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: true
) will no longer throw errors for built-in events, such as done.state.*
or done.invoke.*
events.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 🔜).
XState
(previously: xstate
).data
property of the StateNode
config is now an "Assigner" or "PropertyAssigner" instead of any
.new State()
constructor now takes an object with properties for each parameter, instead of positional arguments.State.create({ ... })
which does the same as above.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.3 !== '3'
when selecting state transitions.actionTypes.null
is now actionTypes.nullEvent
, which alleviates some autocomplete issues in VS Code, etc.params
and content
, data
will be used (polymorphic property - can take an "Assigner" or "PropertyAssigner")'done.state'
event. #224process.env.NODE_ENV
check will no longer produce errors in browser environments. #227Machine
interface was renamed to StateMachine
to prevent naming conflicts. #231history
property to indicate a history state is deprecated (use type: 'history'
instead).State
instance
state.changed
property indicates whether a state has changed from a previous state. A state is considered changed if:
value
is different from the previous state value
value
is unchanged but there are actions
to be executed on the new statestate.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
{}
).parallel: true
is deprecated; use type: 'parallel'
instead.History States
type: 'history'
and history: 'parallel'
or history: 'deep'
(parallel by default). See https://xstate.js.org/docs/guides/history/ for more details.'$history'
string is deprecated.Final States
type: 'final'
. See https://xstate.js.org/docs/guides/final/ for more details.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
invoke
method. See https://xstate.js.org/docs/guides/communication/ for more details.Interpreter
xstate
. See https://xstate.js.org/docs/guides/interpretation/ for more details.Assign and context
context
, which is the extended state of the machine.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
after: ...
property of a state node config:{
after: {
1000: 'yellow'
}
}
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.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!
machine.getStateNodeByPath(['foo', 'bar', 'baz'])
(documentation pending)There's so many exciting improvements and features in this release. In general, the internal algorithms for determining next state, as well as actions, activities, events, and more were heavily refactored to adhere closer to the SCXML spec, as well as be easier to understand and maintain in the codebase. There's always room for improvement though, and we're always open to PRs!
onEntry
, onExit
, actions
) can now be named functions, which should make authoring statecharts easier. https://github.com/davidkpiano/xstate/issues/47 (📖 Docs)
extState
(the external state passed into the transition(...)
method) and event
(the event object that caused the transition)cond
on transition configs) can now be strings, and referenced by the guards
object in the machine config. https://github.com/davidkpiano/xstate/issues/57 (docs coming soon!)const enoughTimeElapsed = (extState, event) => {
return event.emergency || extState.elapsed > 300;
};
const lightMachine = Machine({
initial: 'green',
states: {
green: {
on: {
TIMER: {
// string conditional
yellow: { cond: 'enoughTimeElapsed' }
},
}
},
yellow: { /* ... */ }
}
}, {
// guard config
guards: { enoughTimeElapsed }
});
$history
magic (this will still work and will be deprecated in 4.0). https://github.com/davidkpiano/xstate/issues/86 (📖 Docs)const historyMachine = Machine({
initial: 'off',
states: {
fanOff: {
on: {
// transitions to history state
POWER: 'fanOn.hist',
HIGH_POWER: 'fanOn.highPowerHist'
}
},
fanOn: {
initial: 'first',
states: {
first: {
on: { SWITCH: 'second' }
},
second: {
on: { SWITCH: 'third' }
},
third: {},
// shallow history state
hist: {
history: true
},
// shallow history state with default
highPowerHist: {
history: true,
target: 'third'
}
},
on: {
POWER: 'fanOff'
}
}
}
});
getShortestPaths
graph function now works with conditional guards when passed an external state. https://github.com/davidkpiano/xstate/issues/100
cond
) can now access the current state value, meaning you can use matchesState
to determine if a transition should occur. #110xstate
is now even closer to full SCXML compatibility (for most use cases, it's already compatible).Special thanks to @mogsie for his contributions!
Plenty of new improvements and features in this release! 🎉
State
object. #45{
green: {
on: { /* ... */ },
data: {
name: 'Green Light'
}
}
}
const myMachine = Machine({
initial: 'G',
parallel: false,
states: {
G: {
on: { UPDATE_BUTTON_CLICKED: 'E' }
},
E: {
on: {
// eventless transition
'': [
{ target: 'D', cond: ({ data }) => !data }, // no data returned
{ target: 'B', cond: ({ status }) => status === 'Y' },
{ target: 'C', cond: ({ status }) => status === 'X' },
{ target: 'F' } // default, or just the string 'F'
]
}
},
D: {},
B: {},
C: {},
F: {}
}
});
id: 'foobar'
prop, for example{ target: '#foobar' }
(or just '#foobar'
).machine.transition('#some-id', 'EVENT')
..childState
syntax. See #71{ target: ['foo.bar.one', 'foo.baz.quo'] }
. #80matchesState
will now return false
if the parent state is more specific than the child state. #69This is the officially published v3 of xstate
! 🎉
Some changes from V1 and V2:
machine.transition
will always return a State
object.onEntry
, onExit
actions for states, and actions
for transition actionscond
for conditional (guarded) transitionsstrict: true
for strict mode (helpful in development)If you're wondering why V3 and not V2: