xstate
xstate copied to clipboard
Bug: Wildcard transition catching done event before onDone handler
Description
In order to catch unexpected events and make sure I handle them, I use a top-level wildcard transition at the root level of my machine. This enables me to transition to an "unexpected event" state and log what happened, etc.
I noticed that when using a compound state with a final child state and an onDone
transition, the wildcard transition is taken instead of the onDone
transition.
Expected result
The onDone
transition is taken.
Actual result
The wildcard transition is taken.
Reproduction
https://stately.ai/viz/6363d58b-2950-4a3a-bee1-f1cbbb73bd02
Additional context
No response
This isn't the most ideal workaround, but you can add a guard to the wildcard transition:
on: {
"*": {
target: "unexpected_event",
cond: (_, e) => !e.type.startsWith("done.state")
}
},
https://codesandbox.io/s/mystifying-darwin-ofvt46?file=/src/index.js:340-458
This is more than likely already fixed in v5 (still in alpha).
Thanks @davidkpiano. I considered a workaround like that. The one downside is that it would make the wildcard transition less useful for catching missing onDone
transitions elsewhere.
I ended up working around this by not using a final child state and instead just using the transition I wanted to make directly:
states: {
task: {
initial: 'starting',
states: {
starting: { on: { TASK_FINISHED: 'finished' }},
finished: '#done'
},
},
done: { id: 'done' },
}
I'm happy to try to do some debugging and put up a PR if you think it would be useful to fix in v4, but if not, no worries.
I'm happy to try to do some debugging and put up a PR if you think it would be useful to fix in v4, but if not, no worries.
Unless @Andarist has any quick ideas, this is probably not a trivial fix; might be better to wait for v5 and use the workaround for now.
The bug is caused by the "incorrect" behavior that was implemented in XState long time ago (IIRC it should already be fixed in v5).
When entering a final state we raise 2 events, in this case, those are:
done.state.done-bug.task.finished
done.state.done-bug.task
The SCXML spec only defines the latter (the done event for the parent state of a final state and not a done event for the final state itself). The given onDone
only defined a transition for the latter but all events are matched in the same way. So the first event goes through the internals, it's ignored by onDone
but matched by the *
that we might find higher in the machine.
Ah that makes sense. Thanks for explaining! Sounds like changing those events would be a breaking change.
I could imagine having a special case to have an onDone
transition catch all done
events from children, but it would probably need to be smart enough to "merge" those two events and only trigger the transition once. I do think that would better match how I would expect onDone
to work.
This works as expected in xstate@alpha
(same code)