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