Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

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.

Automatic traffic light state machine 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 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.

Posted in Application Development
Share this

Santiago Kent

Santiago Kent is a Full Stack Developer at Modus Create, passionate about writing code and shipping features. He is dedicated to learning new things and improving processes in just about everything.
Follow

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers Old
  • Let’s talk
  • EN
  • FR