Oftentimes when we hear the words finite state machine or statecharts, the first thing that crosses our mind is that they are great theoretical concepts but our application just seems too dynamic, complex and impossible to model with a finite number of states.
"Finite state machines sound great in theory but I can't know upfront know how many users or todos my application will have"
Luckily, we don't need to know the possible states of our dynamic content upfront. We can model things like arrays, numbers, strings or even more complex types with a feature of statecharts called extended state
. It can receive any arbitrary value that can't easily be constrained to a finite number of states. In XState, extended state
is called context
.
Even though in theory, we can put any arbitrary value in our context
, practically (when using TypeScript) we do want to put some constraints by defining types.
Let's create a thermostat machine that can either be actively monitoring our temperature or be turned off/inactive.
interface ThermostatContext {
/**
* In degree Celsius
*/
temperature: number;
}
interface ThermostatStateSchema {
states: {
active: {};
inactive: {};
};
}
While the possible power state of our thermostat can easily be represented with a finite number of states (active | inactive
), the temperature is a very volatile variable with a great amount of possible values. In fact, if our temperature had an infinite number of decimal places, the value can be in an infinite number of possible states. As a result, there is no point in modeling the possible states (like -3 | -2 | -1 | 0 | 1 | ...
) as state nodes. Instead, we express this dynamic state as context
. We'll see how to set the initial state of the context
in a second but let's finish our type definitions by creating the events first.
type ISetTemperatureEvent = {
type: 'SET_TEMPERATURE';
temperature: number;
};
type ISettingsEvent =
| { type: 'POWER_TOGGLE' }
| ISetTemperatureEvent
Now that we have defined all our types, we can implement our statechart as seen below.
const thermostatMachine = Machine<ThermostatContext, ThermostatStateSchema, ThermostatEvent>({
id: 'thermostat',
initial: 'inactive',
context: {
temperature: 20,
},
states: {
inactive: {
on: {
POWER_TOGGLE: 'active'
}
},
active: {
on: {
POWER_TOGGLE: 'inactive',
}
},
}
});
We define the context property at the root of our machine and assign an initial value of 20°C to the temperature property.
Our machine is almost done. The only thing missing is to implement the SET_TEMPERATURE
event. We'll add it tomorrow as we'll have to introduce some new vocabulary before we can assign a new value to the context. This post was just a brief introduction to extended state while the post tomorrow will explain thoroughly how to manipulate it.
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 spent on the fundamentals before we'll progress to more advanced concepts.
Top comments (0)