State Modeling in React with XState Hierarchical States Solution
Transcript from the "Hierarchical States Solution" Lesson
>> From my playing around with it here, it seems like you cannot transition to a parent state from within a nested hierarchical state. Does that sound right?
>> So the question was about transitioning basically to any state, whether it's a parent or another state from each child state.
[00:00:20] And the way you want to do that is by using ids. So for instance, if I wanted to somehow transition from a child state, we'll take a look at this final one, transition from this normal state on reset go to, I don't know, just paused or something, it doesn't matter.
[00:00:45] What you would do is you would specify this date target via that hash, so #paused. And then you would give this an id of paused, so that's how you would do that. The reason it is awkward is because it's supposed to be awkward. Ideally, you should have your logic and your transitions so that you basically stay in that parents dates and you don't try to escape it.
[00:01:19] You could think of it like a parent-child relationship. A child should never tell an adult what to do, but then adults can tell children what to do. I don't know if that's a good analogy, but hopefully that resonates with some people. All right, so let's quickly go over this exercise real quick.
[00:01:40] The goal was to separate running into nested or hierarchical states, we wanted two states both normal and overtime. And so let's go ahead and add that here. So we have an initial state of normal, and then we have two child states which is normal and overtime. So we added the initial state.
[00:02:11] It's going to start in that normal state. And so ticking, no matter which child state it is in, that ticking behavior is still going to do the same thing. It's still going to increments the context of the lapse by that interval and that's exactly what we wanted to happen.
[00:02:30] We also want toggle pause to only happen in the normal state. And adding a minute to only happen in the normal state as well, according to the read me. So let's just try this out now. So if we go to exercise 6, we'll see that we're in the running.normal state.
[00:02:56] And once we go overtime, aha, so it's not going overtime because we haven't added that transition that we always go to the overtime states when the elapsed time exceeds the total duration. So we could say always go, oops, always go to the overtime state on the condition that context.elapsed is greater or equal to context.duration.
[00:03:37] So now let's try that out. We're in the normal state and so once that condition hits true, now we're in the overtime state. And it's going to keep decrementing or incrementing that overall time, which shows that negative number, showing how far past the total duration it is, and then we could reset.
[00:04:02] Now also we do need to add a few forbidden transitions. For example, we don't want reset to do anything in running.normal. So this sort of goes to the original question of how can we target parent states. You don't always need to do that. Instead you could take a defensive approach and say, for example, on reset, undefined, so that creates a forbidden transition.
[00:04:32] So that even if we're in that running state, the reset button isn't even there, but it's not going to do anything if it is clicked, so. Also, you see that little bug there. We're pausing it even though we're in the overtime state. So in the overtime state we could say on PAUSED undefined.
[00:04:58] And so now, sorry on TOGGLE undefined. So basically the only thing you could do in the overtime state is reset the timer. And so when it goes here, you can't pause it, you can only reset it and that's what that button is for. So are there any questions about nested hierarchical states and that exercise, in general?
>> Could you just clarify, again, for me a little bit so about what you were saying earlier about parents not taking orders from their children, which makes sense in real life [LAUGH]? But so were you basically saying that you shouldn't define transitions from child states, basically? And that those transitions should always be controlled by the parents?
[00:05:56] Could you just kind of expound or clarify on that a little bit?
>> So the question was about just whether children should defy their parents and transition directly to other states. Again, the way to do that is, you would have something like some event, and then you would go to a specific state, like paused for instance.
[00:06:25] And then you would also give this date an id of paused. While you can certainly do that, and there's actually nothing really wrong with it, that becomes more of a modeling problem where you want to basically see what patterns you could use to your advantage. So think about the logic in terms of business logic, just human terms like what should happen in each states.
[00:06:55] Obviously, if you're in a specific sub states and an event happens, it's definitely well within the design in the way that state charts work, that you could have any sub states go to any other parent states. So honestly, that's really a matter of opinion whether you want to directly transition to other states or not.
[00:07:16] There's also other advanced techniques such as defining final states, and actually, I could show you that real quick. So let's say that we have this overtime state, this is actually a pretty good lesson. So if we're in this overtime state, let's say that we want to define some logic that says, after two seconds, we essentially want to go to the reset state.
[00:07:45] So basically when we reset, we go back to idle, right? We could do this. We could specify the state via id or the target state, and give this an id of idle as well. By the way, by default the id of each state is a combination of, let's call this timer, is the combination of the machine's id and the id of the state.
[00:08:17] So this would be timer.idle, this might be timer.running.normal, so etc, etc. But anyway, we could do this. We could say, After five seconds, and then it automatically goes, or sorry, let's reload that. It should automatically go to the idle state after a couple of seconds, so one, two, and then it goes back to idle.
[00:08:51] Now there we're doing a direct transition from overtime, that child state, over to idle, but there is a better way of doing this. We could define a final state, so let's call this timeOver. Naming things is hard, thankfully with state machines you could just name them whatever, as long as it it makes sense in a logical context.
[00:09:23] So now after 2000 seconds, we could go to that timeOver state and then we could say that this state is a final state. Now this isn't gonna do anything on its own, but on the parent state, we could say onDone, actually not over there. We could say onDone, which basically means, if this parent state has reached a final state of its children, so if one of its children is in a final state, then we want to go to idle.
[00:09:59] Now notice I'm not using the hash here, because this running state is a sibling of this idle state, so we don't even need this anymore. And now we could, let me reload running.normal, after five seconds, it's gonna go to overTime. And then after a couple of seconds, it's gonna go to idle.
[00:10:20] So what's happening there is it's going to that final state and where's that final state, this timeOver state. And because it is in that final state, this onDone transition gets called. So it basically immediately happens that way. So that's one of the ways that you can model it.
[00:10:39] And it actually might be more clear. Depending on the business logic and application logic, it might be more clear to just have that explicit timeOver state and saying, when my child states are done, so when it reaches a final state, go to this date. But again, it's up to you, modeling is an art.
[00:10:59] It's not a hard science, so yeah.