Bug
When firing a send action in a transient state, a RangeError: Maximum call size stack exceeded is encountered.
This is working as expected, and indicates some faulty logic in the statechart design. Basically, this logic:
decide: {
on: {
"": [
{
cond: ctx => ctx.value < 0.5,
actions: send("LOWER")
},
{
cond: ctx => ctx.value >= 0.5,
actions: send("HIGHER")
}
],
says, in plain English:
When you transition to the
decidestate, transition back to thedecidestate. Also, always send theLOWERorAFTERevent after transitioning depending on if the value is higher or lower.
That's infinitely recursive 馃槰
Instead, consider modeling this as having an idle state (which you already have) which is a "resting" state for after you've already decided:
Got it. Transient states require a target.
We'll make sure to head over to another state before firing off a send like that.
Using send in the way we do stems from the need to target a state without IDs. We use a single statechart to describe an entire app, and use mixins to separate sections (we did manage a structure where each section was a separate invoked Machine instance, but passing data back and forth became very cumbersome. Clean, but cumbersome. A single context is a must for us).
However, re-using these "sections", i.e. child states, proves problematic as the same child states could be mixed in more than once. For example, imagine a "modal" statechart that is mixed in twice in the main "app" statechart, but in different places.
We end up leveraging send to communicate back to the parent state, and letting it decide where to go next.
e.g.
const acceptConfig = {
initial: 'display',
states: {
'display': {
on: {
CLICK_ACCEPT: 'accepted'
}
},
'accepted': {
type: 'final',
onEntry: send('ACCEPTED')
}
}
}
const appConfig = {
initial: 'app',
states: {
'app': {
initial: 'intro'
states: {
'intro': {
on: {
NEXT: 'terms'
}
},
'terms': {
on: {
BACK: 'intro',
ACCEPTED: 'eula'
},
...acceptConfig
},
'eula': {
on: {
BACK: 'terms',
ACCEPTED: 'home'
},
...acceptConfig
},
'home': {
}
}
}
}
}
The bubbling mechanics of send are perfect here. I just wish the same mechanics applied to target, i.e. look for a sibling state, if not found, go up a level, look for a sibling state, if not found, go up a level, and so on. It seems the direction is more "id" based though, based on this conversation (https://github.com/davidkpiano/xstate/issues/52).
EDIT: in re-reading my example, bubbling a state change isn't actually better than using send. I'm not sure what the cleaner way of doing this is, where you aren't using actions for this scenario... the solution may deviate from the SCXML too much, I don't know. Anyway, just thought I'd share this use case with you, as it underpins many of our apps now.
Most helpful comment
This is working as expected, and indicates some faulty logic in the statechart design. Basically, this logic:
says, in plain English:
That's infinitely recursive 馃槰
Instead, consider modeling this as having an
idlestate (which you already have) which is a "resting" state for after you've already decided:https://codesandbox.io/s/l29y6zmo77