Releases: statelyai/xstate
v4.2.0
- ➕ 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 providescontext
andevent
as arguments and returns anEvent
:
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 onState
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 asdone.state.*
ordone.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
.npmignore
was removed ❌- The
"files"
property was added instead to ensure only the desired files show up in the NPM package.
v4.1.0
- For browser builds, the global export was renamed to
XState
(previously:xstate
). - The
data
property of theStateNode
config is now an "Assigner" or "PropertyAssigner" instead ofany
. - 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 takessrc
as a second argument. For errors from an invoked service, the service ID will populate thesrc
. 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 nowactionTypes.nullEvent
, which alleviates some autocomplete issues in VS Code, etc.- Instead of
params
andcontent
,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 toStateMachine
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 (usetype: 'history'
instead).
v4.0.1
v4.0.0
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 statevalue
- its
value
is unchanged but there areactions
to be executed on the new state
- its
- 🆕
state.nextEvents
property represents all next possible events from the current state. - 🆕
state.matches(parentStateValue)
is equivalent to thematches()
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; usetype: 'parallel'
instead.
History States
- 🆕 History states are explicitly defined with
type: 'history'
andhistory: 'parallel'
orhistory: 'deep'
(parallel by default). See https://xstate.js.org/docs/guides/history/ for more details. - 💥 The magic
'$history'
string is deprecated.
Final States
- 🆕 Final states are now implemented and specified with
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
- 🆕 Declaratively invoke other statecharts (or plain Promises) using the
invoke
method. See https://xstate.js.org/docs/guides/communication/ for more details.
Interpreter
- 🆕 There is now an (optional) interpreter shipped with
xstate
. See https://xstate.js.org/docs/guides/interpretation/ for more details.
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 currentcontext
andevent
. - 🆕
after(delay, id)
creates an event for the given state nodeid
that represents a delay ofdelay
ms. - 🆕
done(id, data)
represents that the parent state node with the givenid
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
v3.3.2
v3.3.1
- History states are only kept on the most recent state. This does not affect existing behavior, and prevents memory leaks. 23c84e2
- Relative transitions are now all properly formatted, through all possible configurations. #117
- Substates with the same key as the parent state will no longer create an incorrect transition. #118
v3.3.0
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!
Features and Improvements
- Actions (
onEntry
,onExit
,actions
) can now be named functions, which should make authoring statecharts easier. #47 (📖 Docs)- They take in two arguments:
extState
(the external state passed into thetransition(...)
method) andevent
(the event object that caused the transition)
- They take in two arguments:
- Guards (
cond
on transition configs) can now be strings, and referenced by theguards
object in the machine config. #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 }
});
- Explicit history states - no more
$history
magic (this will still work and will be deprecated in 4.0). #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'
}
}
}
});
- The
getShortestPaths
graph function now works with conditional guards when passed an external state. #100 - Guard functions (
cond
) can now access the current state value, meaning you can usematchesState
to determine if a transition should occur. #110 - Lots of tests have been added (over 300 now!) and
xstate
is now even closer to full SCXML compatibility (for most use cases, it's already compatible).
Special thanks to @mogsie for his contributions!
v3.2.0
Plenty of new improvements and features in this release! 🎉
- Support for metadata in state nodes, which are returned in the resulting
State
object. #45
{
green: {
on: { /* ... */ },
data: {
name: 'Green Light'
}
}
}
- Transient states (with eventless transitions) and conditional transition arrays supported. #43
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: {}
}
});
- Partial support for SCXML conversion - full support coming 🔜
- State nodes can now be targeted directly via ID.
- A state node can have the
id: 'foobar'
prop, for example - A transition can target that ID via
{ target: '#foobar' }
(or just'#foobar'
).
- A state node can have the
- IDs can also be passed directly to
machine.transition('#some-id', 'EVENT')
. - Partial support for internal (local) transitions, using
.childState
syntax. See #71 - Multiple targets supported with array syntax, e.g.,
{ target: ['foo.bar.one', 'foo.baz.quo'] }
. #80 - Just like conditions, now you can used named functions as actions instead of just strings!
matchesState
will now returnfalse
if the parent state is more specific than the child state. #69