xstate icon indicating copy to clipboard operation
xstate copied to clipboard

Bug: Wildcard transition catching done event before onDone handler

Open jonahkagan opened this issue 2 years ago • 5 comments

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

jonahkagan avatar Aug 12 '22 21:08 jonahkagan

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).

davidkpiano avatar Aug 13 '22 00:08 davidkpiano

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.

jonahkagan avatar Aug 13 '22 16:08 jonahkagan

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.

davidkpiano avatar Aug 13 '22 19:08 davidkpiano

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.

Andarist avatar Aug 13 '22 19:08 Andarist

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.

jonahkagan avatar Aug 13 '22 19:08 jonahkagan

This works as expected in xstate@alpha (same code)

davidkpiano avatar Jan 28 '23 23:01 davidkpiano