# Timed Finite State Machines with React and XState

For a while now I keep noticing tweets showing up on my timeline about state machines in React. Especially I keep stumbling upon the XState library. XState is a library for creating state machines and interpreting them. I’ve never used XState or other similar libraries before but a long time ago during my studies I've had to deal a bit with finite state machines. In particular I remember I enjoyed playing around with Timed Automatons. A timed automaton is a finite-state machine extended with clock variables. To demonstrate what a timed automaton is I will use XState with React.

We are going to build the following machine depicting a pretty simple lamp.

The lamp is composed of three states: `Off`, `Low` and `Bright`. We move through the states by clicking on the button. If we click on the button then the lamp turns on. If we click on the button again then the lamp will turn off. However, if we are fast and we rapidly click the button twice, the lamp turns on and becomes bright. The clock of the lamp is used to detect if we were fast `t < 3` or slow `t >= 3`.

The clock `t` is used as a `guard` during the transitions. The clock starts ticking (is reset) when we move from the `Off` state to the `Low` state. While we are in the `Low` state the clock continues ticking and depending how long we stay in that state determines our next transition. In this case if we act in less than 3 seconds `t < 3` we would transition to the `Bright` state, and from the `Bright` state on the next transition we end up in the `Off` state. Otherwise if we stay longer than `t >= 3` in the `Low` state on the next transition from there we end up in the `Off` state.

### Implementation

Now that we have the model, let's see how the LampMachine.ts looks like with React and XState.

We create a simple machine with an `id: "lamp"` that has only three states represented by the `LampState` enum, and we set the initial state to `LampState.Off`

``````import { assign, createMachine, send } from "xstate"; enum LampState {
Off = "Off",
Low = "Low",
Bright = "Bright",
}
enum Event {
Tick = "Tick",
Reset = "Reset",
}
interface LampContext {
elapsed: number;
clockGuard: number;
interval: number;
} export const createLampMachine = ({ elapsed, interval, clockGuard }: LampContext) =>
createMachine<LampContext>({
id: "lamp",
initial: LampState.Off,
context: {
elapsed,
interval,
clockGuard,
},
states: {
[LampState.Off]: {
...
},
[LampState.Low]: {
...
},
[LampState.Bright]: {
...
},
},
});``````

The `LampState.Off` and `LampState.Bright` transition implementation are pretty simple so we will look only into the `LampState.Low` node where the interesting stuff happens. Upon entering this node we invoke a service that starts a timer. Inside the interval we send an event `Event.Tick` that is used to update the `elapsed` counter in the context.

``````[LampState.Low]: {
// The timer service
invoke: {
src: (context) => (cb) => {
const interval = setInterval(() => {
// Send the event
cb(Event.Tick);
}, 1000 * context.interval);
return () => {
clearInterval(interval);
};
},
},  on: {
// On every tick
[Event.Tick]: {
actions: assign({
// Update the elapsed counter
elapsed: (context) => context.elapsed + context.interval,
}),
},
...
}
}``````

Additionally in the `LampState.Low` state we also have the `click` event implementation where we handle the guards. In the `click` event we specify an array with two conditions. In XState a condition function (also known as a guard) is specified on the `.cond` property of a transition. From the guard functions we return a boolean `true` or `false`, which determines whether the transition should be allowed to take place.

``````[LampState.Low]: {
...
on: {
...
click: [
{
// We transition to the `Bright` state if `t < 3`
target: [LampState.Bright],
cond: ({ elapsed, clockGuard }): boolean => {
return elapsed < clockGuard;
},
},
{
// Otherwise transition to the `Off` state if `t >= 3`
target: [LampState.Off],
cond: ({ elapsed, clockGuard }): boolean => {
return elapsed >= clockGuard;
},
},
],
}
}``````

In the above code we can see that if we satisfy the condition `t < 3` we transition to the `Bright` state. Otherwise if the `t >= 3` condition is satisfied we transition to the `Off` state. And that is more or less how we handle clock variables in the lamp state machine.

Finally if we take a look at the React component, the usage is straight forward. I am using Jotai for this example just for fun, but it can totally work without it.

``````import { atom, Provider, useAtom } from "jotai";
import { atomWithMachine } from "jotai/xstate"; // We initialize the `lampMachineAtom` with some default values.
// We can specify the guard depending how long we want to stay in the `Low` state.
// In this example we are waiting 3 seconds
const defaultAtom = atom({ elapsed: 0, interval: 0.1, clockGuard: 3 });
const lampMachineAtom = atomWithMachine((get) => createLampMachine(get(defaultAtom))); const Lamp: React.FC = () => {
const [state, send] = useAtom(lampMachineAtom);
const { elapsed, clockGuard } = state.context;
...  const getLampState = () => {
if (state.matches(LampState.Off)) {
return LampState.Off;
}
if (state.matches(LampState.Low)) {
return LampState.Low;
}
if (state.matches(LampState.Bright)) {
return LampState.Bright;
}
return LampState.Off;
}; return (
<>
...
{/* The Button we use to control the lamp */}
<Switch onClick={send}>{getLampState()}</Switch>
{/* Timer to see the clock running */}
<h2 className={styles.counter}>t={elapsed.toFixed(1)}s</h2>
{/* The Automaton to show the simulation step by step */}
<Automaton light={getLampState()} clockGuard={clockGuard} elapsed={elapsed} />  <LightBulb light={getLampState()} />
...
</>
);
}``````

And pretty much that's it. You can find the entire source code on GitHub if you want to check it out.

### References

Finite-state Machines

Timed Automaton

A Tutorial on Uppaal 4.0

I had a lot of fun playing around with XState this weekend, most likely I will use it in the future as well.

Are you using XState in your projects? Tweet me at @altrimbeqiri and let me know the cool stuff you build with it.

Happy Coding!