Releases: statelyai/xstate
v4.2.4
- 🕵️ 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 onstate.actions
). #295 - 📖 Documentation updates:
- Forbidden transitions
- Transient state nodes
- Various formatting/fixes
- 🕐 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
v4.2.2
- 📚 New & updated docs:
- 🔬
state.changed()
logic fixed to better determine if a state actually changed. - 💥 Action order corrected to properly execute nested final state actions before parent final state actions. #273
v4.2.1
- 📚 Lots of documentation updates: https://xstate.js.org/docs
- 💥 Added the
.event
property toState
instances, so you can know which event caused the transition to the currentState
:
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 executingonEntry
actions.
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!