Inย part 1, you learned about the concepts behind state machines, how to create a basic working machine using XState, and integrate that into a React component.
In the second part, we will make our traffic light run automatically and turn it ON and OFF.
This article will assume you already read part 1. So if you didn’t, pleaseย check it out.ย
Last time, our state machine modeled a traffic light that changed at the press of a button. Now it’s time to add more features.
Making the Traffic Light Automatic
For a more realistic implementation, the button needs to be replaced by a timer. There are several ways to implement it. For this example, the choice will be aย Delayed Transitionย โ a transition that occurs automatically after a delay.
export const trafficLightMachine = createMachine< undefined, TrafficLightEvent, TraffiLightState >({ id: "trafficLight", initial: "red", states: { green: { on: { NEXT: "yellow" }, after: { 3500: "yellow", }, }, yellow: { on: { NEXT: "red" }, after: { 1500: "red", }, }, red: { on: { NEXT: "green" }, after: { 5000: "green", }, }, }, });
These delayed transitions are defined in the afterย property, which maps the delay in milliseconds to the respective state it will transition to.
So, 3.5 seconds after entering theย greenย state, it will automatically transition to theย yellowย state. Which, in turn, will transition to theย redย state after 1.5 seconds, and red will transition over toย greenย after 5 seconds, completing the familiar traffic light loop.
After making this change, the lights will start changing automatically, just like a real traffic light.
You can still use theย NEXT button, and it will change lights immediately. Let’s run our automatic traffic light machine through the visualizer again.
Our automatic traffic light state machine
If you do it yourself, you’ll notice that the automatic transitions also happen on the visualizer! This is incredibly helpful to visualize complex business logic and a great way for designers and product managers to visualize how the application works.
Hierarchical States
A new requirement just came in. We need to be able to turn the traffic light on and off. So it’s time to extend our machine definition.
The machine needs to be turned on and off, which required adding two more event types to our model.
type TrafficLightEvent = | { type: "NEXT" } | { type: "TURN_OFF" } | { type: "TURN_ON" };
Now to our states, when the machine is ON, the lights should cycle automatically, and when we are on the OFF state, the lights need to be all off. This is best represented byย hierarchical states โย the OFF state would be a simple state, and the ON state should also be able to house our existing states. Let’s see how that looks.
type TraffiLightState = | { value: { ON: "green" }; context: undefined } | { value: { ON: "yellow" }; context: undefined } | { value: { ON: "red" }; context: undefined } | { value: "OFF"; context: undefined }; export const trafficLightMachine = createMachine< undefined, TrafficLightEvent, TraffiLightState >({ id: "trafficLight", initial: "OFF", states: { ON: { on: { TURN_OFF: "OFF" }, initial: "red", states: { green: { on: { NEXT: "yellow" }, after: { 3500: "yellow", }, }, yellow: { on: { NEXT: "red" }, after: { 1500: "red", }, }, red: { on: { NEXT: "green" }, after: { 5000: "green", }, }, }, }, OFF: { on: { TURN_ON: "ON" }, }, }, });
Let’s break up the changes, starting with our Typestates definitions. We added a case for theย OFFย state and also modified our color states. They now have a parentย ONย state, represented by the object notation.
Now to the machine itself, we need to refactor our first level of states definition. We created anย ONย and anย OFFย state. Theย OFFย state is a simple state in which we define a single transition. It should transition over toย ONย when the TURN_ON event is received.
We defineย statesย again on the newย ONย state, with our previous color states and transitions. The lights are nowย substatesย of the parentย ONย states. We also changed the initial state of the entire machine. It now starts withย OFF.
As this might be a little intimidating, let’s visualize our machine again to avoid any confusion.
Hierarchical traffic light
React Integration
Now that we’ve made the necessary changes to our state machine, we need to update our react integration to handle the new states and events.
export const App = () => { const [current, send] = useMachine(trafficLightMachine); return (
); };
As you can see, not much has changed; we’ve added two new buttons to turn ON and OFF the traffic light. But the most significant change is how our matches function changes to match the color that should be lit. We need to check if the machine matches the substateย of ON, soย current.matches({ ON: ‘green’})ย will only be true if the machine isย ONย and substate green.
If we run our application in the browser, we can see that the traffic light starts with all lights off, and we can turn it on to the light cycle using the TURN ON button.
Wrapping up
We’re now starting to see the true power of state machines! We explored how Delayed Transitionsย can make automatic transitions possible without writing too much code and how theย Hierarchical Statesย can help us model more complex behaviors without losing confidence, type safety, and code readability.
You can review the sample codeย here.