Releases: statelyai/xstate
xstate@4.7.5
Patch Changes
- 6b3d767: Fixed issue with delayed transitions scheduling a delayed event for each transition defined for a single delay.
@xstate/test@0.2.1
v4.7.0
- 🐌 If a subscriber/listener subscribes to a service that is already running, it will now receive the current state upon subscription. #814
- 🆙 The new
escalate()
action creator escalates custom error events to a parent machine, which can catch those in theonError
transition:
import { createMachine, actions } from 'xstate';
const { escalate } = actions;
const childMachine = createMachine({
// ...
// This will be sent to the parent machine that invokes this child
entry: escalate({ message: 'This is some error' })
});
const parentMachine = createMachine({
// ...
invoke: {
src: childMachine,
onError: {
actions: (context, event) => {
console.log(event.data);
// {
// type: ...,
// data: {
// message: 'This is some error'
// }
// }
}
}
}
});
- ❓ You can now specify
undefined
as the first argument formachine.transition(...)
, which will default to the initial state:
lightMachine.transition(undefined, 'TIMER').value;
// => 'yellow'
-
🤝 Services (invoked machines) are now fully subscribable and can interop with libraries that implement TC39 Observbles like RxJS. See https://xstate.js.org/docs/recipes/rxjs.html for more info.
-
🆔 When a service is invoked, it has a uniquely generated
sessionId
, which corresponds to_sessionid
in SCXML. This ID is now available directly on the state object to identify which service invocation the state came from:state._sessionid
#523 -
⚙️ The original
config
object passed toMachine(config)
(orcreateMachine(config)
) is now the exact same object reference in the resultingmachine.config
property. -
🎰 The
createMachine()
factory function now exists and is largely the same asMachine()
, except with a couple differences:- The generic type signature is
<TContext, TEvent, TState>
instead of<TContext, TStateSchema, TEvent>
. - There is no third argument for specifying an initial
context
. Use.withContext(...)
on the machine or return a machine with the expectedcontext
in a factory instead.
- The generic type signature is
-
🛑 Event sent to a stopped service will no longer execute any actions, nor have any effect. #735
-
🚸 The invoked actors are now directly available on
state.children
. -
✍️ Plain strings can now be logged in the
log()
action creator:
entry: log('entered here', 'some label')
- 🚄 Transitions are now available on
state.transitions
, which is an array of transition objects that detail exactly which transitions were enabled to transition to this state. This will be ignored in serialization. - ⏩ New action creator:
forwardTo()
https://xstate.js.org/docs/guides/actions.html#forward-to-action - ➕ Assigners now have a third
meta
argument passed in, which contains meta data such as thestate
and the originalaction
- *️⃣ New event type: the wildcard! https://xstate.js.org/docs/guides/transitions.html#wildcard-descriptors
const quietMachine = Machine({
id: 'quiet',
initial: 'idle',
states: {
idle: {
on: {
WHISPER: undefined,
// On any event besides a WHISPER, transition to the 'disturbed' state
'*': 'disturbed'
}
},
disturbed: {}
}
});
quietMachine.transition(quietMachine.initialState, 'WHISPER');
// => State { value: 'idle' }
quietMachine.transition(quietMachine.initialState, 'SOME_EVENT');
// => State { value: 'disturbed' }
- 📲 New action creator:
respond()
https://xstate.js.org/docs/guides/actions.html#respond-action - ✅ New typechecking feature: Typestates https://xstate.js.org/docs/guides/typescript.html#typestates-coming-soon
- 📄 SCXML event metadata is now available on
state._event
and also in action/guard/etc. meta under_event
. https://www.w3.org/TR/scxml/#InternalStructureofEvents
v4.6.7
- A regression in the new
stateUtils.ts
file caused a bundling failure; this is now fixed.
v4.6.6
- A configuration change that included testing files as an included path in
tsconfig.json
was reverted.
v4.6.5
Improvements
v4.6.4
Features
- 🔭 Actors that are synced (with
{ sync: true }
) will have their updates reflected asstate.changed === true
. - 📻 This also goes for manual updates (via
sendParent(actionTypes.update)
), when actors are not synced.
Fixes
- 🤦♂
@vuepress/plugin-google-analytics
was accidentally specified as adependency
and not adevDependency
. This is fixed. XState will always be dependency-free.
v4.6.3
v4.6.2
Features
- 🎭 Machines can now keep in sync with spawned child machines when setting
spawn(..., { sync: true })
(false
by default). This means that the parent machine will receive a special"xstate.update"
action with the updated actor state and the actor ID:
// ...
actions: assign({
myTodo: spawn(todoMachine, { sync: true }) // keeps myTodo.state in sync
})
// ...
This will keep sync with the referenced actor's state, by mutating the actorRef.state
property with the new actor state. When doing change detection, do not rely on actorRef
to change - that will always stay constant (unless reassigned). Instead, keep a previous reference to its state and compare it:
const state = currentState.context.myTodo.state;
// ... assume an "xstate.update" event was sent
const nextState = currentState.context.myTodo.state;
state === nextState;
// => false
{ sync: true }
because an "xstate.update"
event will occur for every single state transition in every spawned actor, which will make the service more "chatty". Always prefer explicit updates.
Fixes
- ⚙️
machine.withContext()
now internally uses original config, not machine definition. #491
@xstate/graph
- 🤝
xstate
is now a peer dependency.
@xstate/react
- 🤖 You can now specify machine config directly in
useMachine(...)
options:
const [current, send] = useMachine(someMachine, {
actions: {
doThing: doTheThing
},
services: {/* ... */},
guards: {/* ... */},
// ... etc.
});
v4.6.0
Actors
- 🎭 Support for actors (read up on the Actor model has landed! Actors are dynamic entities that can send and receive messages from each other, spawn other actors, and only modify their own local state. This is a great model for communicating state machines. Think of it like
services
but dynamic (same implementation!).
Read the docs 📖 on actors.
const todoMachine = Machine({
id: 'todo',
// ...
});
const todosMachine = Machine({
id: 'todos',
context: {
todos: []
},
// ...
on: {
'TODOS.ADD': {
actions: assign({
todos: (ctx, e) => [
...ctx.todos,
{ data: e.data, ref: spawn(todoMachine) }
]
}
},
'TODO.COMPLETE': {
actions: send('COMPLETE', {
to: (ctx, e) => ctx.todos
.find(todo => todo.id === e.id)
.ref
})
}
}
});
Observables
- 🔵 Observables can now be invoked (or spawned):
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';
// ...
const intervalMachine = Machine({
id: 'interval',
context: { value: 1000 },
invoke: {
src: (context) => interval(context.value)
.pipe(map(n => ({ type: 'UPDATE', value: n })))
},
on: {
UPDATE: { /* ... */ }
}
// ...
});
- 🔭 Interpreted machines (services) are now observable - they implement the TC39 Observable interface:
const service = interpret(machine).start();
// Subscribe to state transitions
const sub = service.subscribe(state => {
console.log(state);
});
// Stop subscribing
sub.unsubscribe();
- 💥 For executing custom actions,
service.execute(...)
can now be configured dynamically with custom action implementations:
const alertMachine = Machine({
id: 'alert',
context: { message: '' },
states: { /* ... */ },
on: {
NOTIFY: { actions: 'alert' }
}
});
const service = interpret(alertMachine, { execute: false })
.onTransition(state => {
// execute with custom action
service.execute(state, {
alert: (context) => AlertThing.showCustomAlert(context.message)
});
})
.start();
service.send('NOTIFY');
// => shows custom alert
- 🎯 The
send()
action creator now supports target expressions for itsto
option:
// string target
send('SOME_EVENT', { to: (ctx, e) => e.id });
// reference target
send('SOME_EVENT', { to: (ctx, e) => ctx.someActorRef });
- 💧 New action creator:
pure()
allows you to specify a function that returns one or more action objects. The only rule is that this action creator must be pure (hence the name) - no executed side-effects must occur in the function passed topure()
:
// Assign and do some other action
actions: pure((ctx, e) => {
return [
assign({ foo: e.foo }),
{ type: 'anotherAction', value: ctx.bar }
];
});
Fixes and enhancements
⚠️ Warnings are now only displayed when not in production mode.- 🚥 Services that are started will now not have any unintended side-effects if
.start()
is called again. - 🕳 Transitions for deep flat parallel state value now resolve as expected (thanks @Andarist for the test) #458 #465
- ❗️ The
service.state
andservice.initialState
are now always defined on anInterpreter
(service) instance, which should fix some tiny React hook issues. - 🔍 Destructuring
{ matches }
from aState
instance will no longer lose itsthis
reference, thanks to @farskid #- #440 - ❓ A service creator that returns
undefined
will no longer crash the service. #430 #431