Releases: statelyai/xstate
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
v4.5.0
- 📬 Sending events with payloads to running services just got a lot easier. You can now specify two arguments:
eventType
(string)payload
(object)
service.send('EVENT', { foo: 'bar' });
// equivalent to...
service.send({ type: 'EVENT', foo: 'bar' });
This is similar to how Vuex allows you to send events. #408
- ➕ You can now send batch events in a running service:
someService.send([
'SOME_EVENT', // simple events
'ANOTHER_EVENT',
{ type: 'YET_ANOTHER_EVENT', data: [1, 2, 3] } // event objects
]);
Actions from each state will be bound to a snapshot of their state at the time of their creation, and execution is deferred until all events are processed (in essentially zero-time). #409
- 🚪 To avoid confusion,
onEntry
andonExit
have been aliased toentry
andexit
, respectively:
// ...
{
- onEntry: 'doSomething',
+ entry: 'doSomething',
- onExit: 'doSomethingElse',
+ exit: 'doSomethingElse'
}
The onEntry
and onExit
properties still work, and are not deprecated in this major version.
- 🗺 Instead of just
true
orfalse
for theactivities
mapping inState
objects, a truthy activity actually gives you the full activity definition. - ⏱ Proper scheduler work eliminates many edge-cases and issues with sync/async event processing, thanks to @jjkola #392
- 🤐 Thanks to @johnyanarella, invoking promises will work with promise-like values in case non-native promises are used. #415 #417
- 👩🏫 The
Interpreter
class is exposed in the main exports. - 🆕 New package:
@xstate/immer
coming soon! More info on this 🔜
Docs
- ✍️ In the examples,
ctx
is spelled out tocontext
ande
toevent
in order to improve readability and comprehension. - 🔭 The RxJS docs 📖 were updated to address #339
- 🚚 The internal transition docs moved into the transitions docs 📖.
- ℹ️ New pages: About 📖 and Goals 📖 (more to be added 🔜)
- 🔘 Docs were added for relative targets (shortcut syntax) in ids 📖 and transitions 📖
- ⏪ Docs on restarting activities 📖 were added
- 🕵️♀️ The
options
argument was missing in theuseMachine
implementation in the React docs 📖 - that's been fixed 😅
v4.4.0
- 💂♀️ Custom guards are now supported. Read the docs 📘:
SEARCH: {
target: 'searching',
// Custom guard object
cond: {
type: 'searchValid',
minQueryLength: 3
}
}
- ⌚️ Delay expressions are now supported. Read the docs 📘 and see #244 for a use-case:
states: {
green: {
after: {
// after 1 second, transition to yellow
LIGHT_DELAY: 'yellow'
}
},
// ...
// Machine options
{
// String delays configured here
delays: {
LIGHT_DELAY: (ctx, e) => {
return ctx.trafficLevel === 'low' ? 1000 : 3000;
},
YELLOW_LIGHT_DELAY: 500 // static value
}
}
- ⚛️ New package:
@xstate/react
provides you theuseMachine
hook from the docs. Read the README 📘
import { useMachine } from '@xstate/react';
import { myMachine } from './myMachine';
export const App = () => {
const [current, send] = useMachine(myMachine);
// ...
}
- 🔧 Redux DevTools now defaults to
false
in the console, in order to prevent random crashes of the extension. To activate it, set{ devTools: true }
in the interpreter options:
// default: { devTools: false }
interpret(machine, { devTools: true });
- ⏹ Activities are no longer started in transient states. This is because transient states are essentially zero-time, since statecharts should transition to the resolved state immediately, so the activities would be started and stopped in the same microstep anyway.
- 🐛
nextEvents
will now be properly populated inState
objects when transitioning from a state value rather than an existingState
object (or when restoring a saved state). - ℹ️ Action implementations can now read the current state (this should not be a common use-case):
actions: {
log: (ctx, e, { action, state }) => {
console.log(state); // logs the State instance
}
}
- ⬆️ TypeScript version bumped up to 3.3.3333
- ❌ Ever experience those annoying errors about sending events to an uninitialized service? Maybe you forgot to
.start
it, or maybe you know it will be initialized and something out of your control (e.g., React's rendering) initializes the service right after you send an event to it. Now, events are deferred by default and will queue up in the uninitialized service until the service is.start()
-ed... and then the events are processed. A warning will still show up to let you know that the service wasn't initialized.- If you prefer the original behavior, set
interpret(machine, { deferEvents: false })
in the interpreter options.
- If you prefer the original behavior, set
- 🚥 Services will now resolve the
State
(or state value) that they are started with, which means you can:
// Start a service from a restored state
someService.start(State.from({ bar: 'baz' }))
// Or from a plain state value
someService.start({ bar: 'baz' });
// Even if the state value is unresolved!
// (assume 'baz' is the initial state of 'bar')
someService.start('bar'); // resolves to State.from({ bar: 'baz' })
- 📊State changes now properly detect context changes. #397
- 🔃 Relative child transitions that are external (
{ internal: false }
) now properly exit and reenter the parent state before transitioning to the child state; .e.g,{ target: '.child', internal: false }
will now behave like{ target: 'parent.child' }
. #376 - ⌛️ Delayed transitions are now properly canceled after a service is stopped.
- ➕ An awesome React calculator demo based on the original calculator statechart by Ian Horrocks was added to the docs. Thanks, Mukesh Soni!
- 🙏 If an invoked Promise throws, it will only send an event if the Promise wasn't canceled. #379
v4.3.3
- 💻 Multiple services now are invoked as expected thanks to @tivac - #367, #368
- 📅 Event object type inference improved. #338
- 🆕 New
StateNode
method:.resolveState(state)
returns a resolved state (with resolved state.value
,.nextEvents
, etc.) in relation to state node (machine):
// partial state
const someState = State.from('red', undefined);
const resolvedState = lightMachine.resolveState(someState);
resolvedState.value;
// => `{ red: 'walk' }`
v4.3.2
v4.3.1
v4.3.0
- 🚚 Improved import/export experience!
The interpreter
is now included in the main module:
- import { interpret } from 'xstate/lib/interpreter';
+ import { interpret } from 'xstate';
As well as common actions, send
, assign
, and sendParent
:
- import { actions } from 'xstate';
- const { send, assign, sendParent } = actions;
+ import { send, assign, sendParent } from 'xstate';
This change is reflected in the documentation as well as the tests.
- ⚛️ Redux DevTools is now supported by XState. This can be turned off/on with the
devTools
property (true
by default):
const service = interpret(someMachine, {
devTools: false // don't show in Redux DevTools
});
ℹ️ Note: JUMP and SKIP actions in the DevTools will have no effect on XState, because there is no reliable way to arbitrarily go from one state to another with state machines, especially if side-effects are executed.
- 👓 Better TypeScript inference for event types is now available thanks to @sangallimarco #303
- 🏷 Service IDs will now take the ID of the machine if one is not specified:
const machine = Machine({ id: 'foo', /* ... */ });
// ...
{
invoke: {
// Invoked child service will have .id === 'foo'
src: machine
}
}
- 📖 Lots of documentation updates, especially with Invoking Services.
- 🐛 Fixed bugs: #326, #329, #331