Conditional logic is everywhere. While state machines reduce conditional logic by eliminating impossible states, there is some conditional logic we want to have within our machines. In particular, when one or the other action should be executed or multiple state transitions exist. We can define such conditional logic using the very concept we've learned yesterday, guards.
By providing an array of possible state transitions, the state transition with the first guard that evaluates to true will determine the next state of our machine. Let's say we want our thermostat to distinctively express whether it is cold or warm. If the temperature is below 18°C, it should go into the cold
state and above, transition to the warm
state.
import { Machine, assign } = 'xstate';
const thermostatMachine = Machine({
id: 'thermostat',
initial: 'inactive',
context: {
temperature: 20,
},
states: {
inactive: {
on: {
POWER_TOGGLE: 'active'
}
},
active: {
initial: 'warm',
states: {
cold: {},
warm: {},
},
on: {
POWER_TOGGLE: {
target: 'inactive',
},
SET_TEMPERATURE: [
{
target: '.cold',
cond: (context, event) => event.temperature < 18,
actions: assign({
temperature: (context, event) => event.temperature,
}),
},
{
// transition without a guard as a fallback.
target: '.warm',
actions: assign({
temperature: (context, event) => event.temperature,
}),
},
]
}
},
}
});
Think of the state transition array as a switch case to determine the next state of a machine. The default
transition can be expressed as a state transition without a guard as seen in the example above.
Notice how we had to duplicate the action to assign the temperature. Similar to when machines transition from one state to another, actions
are only executed if no guard is defined, or when a guard evaluates to true.
To give one more example of this behavior, the code below will never call the 'log' action.
[
{
target: 'cold',
cond: () => false,
actions: 'log',
},
{
target: 'warm',
},
]
Tomorrow we'll refactor the thermostatMachine
so that we don't have to define the same action twice.
About this series
Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.
The first couple of days will be spend on the fundamentals before we'll progress to more advanced concepts.
Top comments (10)
Hi Mikey, really enjoying this series as a thorough introduction to XState. Not having the ability to run some experiments myself right now, I'm curious why you set
target: '. warm'
rather than justwarm
. Does it carry a meaning?Hey Joel, glad you are enjoying the series. 😊
I mostly use target as I like being explicit and it makes adding a guard or action easier. You can totally omit it if you prefer the shorthand notation.
Right, I'm referring in particular to the prefix dot though, didn't see that syntax explained anywhere. Is
.warm
different towarm
in any meaningful way?Ah right. The dot is added (and needed) for the transition to be recognized as an internal transition. We don't want the machine to leave the
active
state. Instead, we are specifying a relative target (e.g.warm
) to transition to theactive.cold
oractive.warm
state.Without the relative target, the machine could think that we are transitioning to a
warm
state by assuming a state structure like the following and would then fail since the state node does not exist.Makes sense! Wasn't trivial to figure out without XState experience though :)
I'm glad you asked! I couldn't figure out a good way to include it in the post as I found the
transition
pages of docs the most difficult to grasp and a bit discouraging for beginners.On day 15 I was also still naive enough to believe I can get away with explaining one concept per day. 😁 Towards the end, I had to ramp up and explain 2-3 things per post to write about most XState features I wanted to cover.
Either way, really happy to have come across your calendar, will make a good basis for me to dive into XState myself!
Thank you.
It's a high reward decision for sure!
Let me know if you are struggling with anything and feel free to send me machines for feedback.
Hi there, very helpful series, so many thanks!
A small thing: you mention the use of the
default
keyword in example above but i couldn't locate it. (.warm
is referred to as a fallback but it doesn't appear to have an explicit use of the keyword.). Maybe DavidKPiano will provide a statemachine linter next. LOL!Thank you a lot. Glad you found it useful.
I meant to say the "default transition" not "default keyword". Fixed it in the post.