eMoosavi
Weekly frontend dev braindumps
State Machines with React and XState
State Management

State Machines with React and XState

Elevate State Management Using Finite State Machines

Jul 04, 2024 - 02:145 min read

State management is one of the cornerstones of a robust React application. While tools like Redux have become staples in the community, there's a fascinating approach that often flies under the radar: state machines. Specifically, we'll delve into using XState with React to manage state in an elegant and deterministic way.

What Are State Machines?

State machines, or finite state machines (FSMs), are mathematical models used to represent systems with a finite number of states. An FSM can be in only one state at any given time, and transitions between states are triggered by events. For example, consider a basic traffic light with three states: Red, Yellow, and Green. The transitions between these states are dictated by timers or sensor inputs.

Installing XState

First, let's get XState installed.

npm install xstate@latest @xstate/react@latest

Now that we have XState installed, we can explore its integration with React.

Defining a Machine

A state machine in XState is defined using the createMachine function. Here's an example of a simple traffic light machine:

import { createMachine } from "xstate";
const trafficLightMachine = createMachine({
  id: "trafficLight",
  initial: "red",
  states: {
    red: { on: { TIMER: "green" } },
    green: { on: { TIMER: "yellow" } },
    yellow: { on: { TIMER: "red" } },
  },
});

In this example, our machine starts in the red state and can transition to green on a TIMER event, then to yellow, and back to red.

Integrating with React

To integrate this machine with a React component, we can use the useMachine hook from the @xstate/react package.

import React from "react";
import { useMachine } from "@xstate/react";
import { trafficLightMachine } from "./path/to/machine";

const TrafficLight = () => {
  const [state, send] = useMachine(trafficLightMachine);

  return (
    <div>
      <div>Current State: {state.value}</div>

      <button onClick={() => send("TIMER")}>Next</button>
    </div>
  );
};
export default TrafficLight;

Stateful Features

Context and Actions

One of the powerful features of XState is the ability to manage extended state and side effects through context and actions. Let's modify our traffic light example to include a timer context:

import { createMachine, assign } from "xstate";

const trafficLightMachine = createMachine(
  {
    id: "trafficLight",
    initial: "red",
    context: { timer: 0 },
    states: {
      red: { entry: "resetTimer", on: { TIMER: "green" } },
      green: { entry: "resetTimer", on: { TIMER: "yellow" } },
      yellow: { entry: "resetTimer", on: { TIMER: "red" } },
    },
  },
  { actions: { resetTimer: assign({ timer: 0 }) } }
);

Transitions with Guards

Guards are conditions that must be met for a transition to occur. For instance, let's say the traffic light can only change from green to yellow if a minimum time has elapsed:

const trafficLightMachine = createMachine(
  {
    id: "trafficLight",
    initial: "red",
    context: { timer: 0 },
    states: {
      red: {
        entry: "resetTimer",
        on: { TIMER: [{ target: "green", cond: "minTimeElapsed" }] },
      },
      green: { entry: "resetTimer", on: { TIMER: "yellow" } },
      yellow: { entry: "resetTimer", on: { TIMER: "red" } },
    },
  },
  {
    guards: { minTimeElapsed: (context) => context.timer >= 5 },
    actions: { resetTimer: assign({ timer: 0 }) },
  }
);

Hierarchical (Nested) States

For scenarios where you need more complex state management, XState supports hierarchical (nested) states. Let's upgrade our traffic light example to include a flashing yellow state in maintenance mode:

const trafficLightMachine = createMachine({
  id: "trafficLight",
  initial: "operational",
  states: {
    operational: {
      initial: "red",
      states: {
        red: { on: { TIMER: "green" } },
        green: { on: { TIMER: "yellow" } },
        yellow: { on: { TIMER: "red" } },
      },
    },
    maintenance: { initial: "flashingYellow", states: { flashingYellow: {} } },
  },
});

Parallel States

XState also allows parallel states, where multiple states can be active simultaneously.

const pedestrianTrafficLightMachine = createMachine({
  id: "pedestrianTrafficLight",
  type: "parallel",
  states: {
    vehicle: {
      initial: "red",
      states: {
        red: { on: { TIMER: "green" } },
        green: { on: { TIMER: "yellow" } },
        yellow: { on: { TIMER: "red" } },
      },
    },
    pedestrian: {
      initial: "dontWalk",
      states: {
        walk: { on: { TIMER: "dontWalk" } },
        dontWalk: { on: { TIMER: "walk" } },
      },
    },
  },
});

Benefits and Challenges

Deterministic State Management

One of the main advantages of using state machines is that they provide a deterministic, predictable way to manage state.

Ease of Debugging

Since state machines have well-defined states and transitions, debugging becomes much easier.

Complexity and Learning Curve

The concepts of state machines might seem complex initially. However, the benefits often outweigh the challenges, especially in large and intricate applications.

Conclusion

XState offers a powerful paradigm for managing state in your React applications. By leveraging the deterministic nature of state machines, you can create more robust and predictable components.

Article tags
reactxstatestate-machinesstate-managementjavascript
Previous article

CSS

Leveraging Advanced CSS Techniques with TailwindCSS

Next article

Tooling and Libraries

Advanced Patterns with React.memo and useCallback