'xstate - how to properly handle transitions errors?
I'm completely new to Xstate and I'm struggling to find help inside the official documentation.
The problem is pretty easy, I'd like to know if an event is triggered when is not suppose to.
I have a basic workflow that is very strict in terms of transitions, for instance, my state can't go from 'pending' to 'processed' without passing from 'uploaded'.
If I use:
stateService.send('PROCESSED')
while the state is in 'pending', the state doesn't change ( correct ) but is there any utility or event in Xstate that actually tells me that the transaction was not fired since the event was not allowed/correct?
This is my state
const stateMachine = Machine(
{
id: 'vocalTrack',
initial: 'PENDING',
context: {},
states: {
PENDING: {
on: {
UPLOADED: 'UPLOADED',
},
},
UPLOADED: {
on: {
PROCESSED: 'PROCESSED',
},
entry: 'onUploaded',
},
PROCESSED: {
on: {
READY: 'READY',
ERROR: 'ERROR',
},
exit: 'onProcessed',
},
READY: {
type: 'final',
},
ERROR: {
on: {
UPLOADED: 'UPLOADED',
},
type: 'final',
},
},
},
{
actions: {
onUploaded: (context, event) => {
console.log(`++++ onUploaded action: ++++`)
},
onProcessed: (context, event) => {
console.log(`++++ onProcessed action: ++++`)
},
},
},
)
const stateService = interpret(stateMachine)
stateService.init('PENDING')
// I'd like to catch the following workflow errors
stateService.send('UPLOADED')
stateService.send('PROCESSED')
Solution 1:[1]
There is no option you can pass to the machine or interpret call that will give you this for "free". You can easily add some logic to the "onTransition" method though:
Option 1, validate on state.changed:
service.onTransition(state => {
if (state.changed) {
console.log(`The state did change, so ${state.event.type} caused a valid transition`);
} else {
console.log(`${state.event.type} did not cause a state change`);
}
})
Option 2, validate based on state.value & event name:
service.onTransition(state => {
if (state.event.type === 'xstate.init'){
console.log('Init Transition');
return;
}
if(state.value === state.event.type){
console.log('Transition Valid');
}else {
console.log('Transition Invalid');
}
})
Option 1 would probably be the preferred solution! You can read more about it here in the documentation. The following SO question is also related in terms of events that don't exist at all.
I've created a small demo application in react to demonstrate this:
Solution 2:[2]
It is a solved issue, but I believe there is also a different one, to handle it on the machine configuration level. So I will post it for comparison.
Taking into consideration that:
- the machine configuration is the same as the state node config, so it supports the
onproperty as well, - if the given state does not handle the given event, it passes it to the parent,
you can define PROCESSED event on the whole machine configuration level:
const stateMachine = Machine(
{
id: "vocalTrack",
initial: "PENDING",
context: {},
on: {
PROCESSED: {
actions: ["invalidCall"]
}
},
states: {
PENDING: {
on: {
UPLOADED: "UPLOADED"
}
},
UPLOADED: {
on: {
PROCESSED: "PROCESSED"
},
entry: "onUploaded"
},
...
},
}, {
actions: {
...
invalidCall: (context, event, meta) => {
console.log(
`invalid call of event ${event.type} on the ${meta.state.value} state`
);
}
}
}
);
and in effect, in every state where the PROCESSED event is not handled, it will propagate up to the main configuration level.
What's more, a wildcard descriptor exists and will capture all events, that is not defined at a given level, so we might as well capture all triggered events that are not supported in a given state with:
on: {
"*": {
actions: ["invalidCall"]
}
},
Of course, it all depends on your particular case, but what I like in that solution, is that it all stays on the machine definition level.
Solution 3:[3]
I also managed to get this working with the following:
export const stateMachine = createMachine({
id: 'stateMachine',
initial: 'created',
strict: true,
on: {
'*': {
cond: (_context, _event, meta) => {
throw new Error(`Cannot change from ${meta.state.value}`);
},
},
},
states: {
created: {
on: {
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | Owen Ben Davies |
