Telerik blogs
ReactT2_1200x303

We’ll look at useStateMachine—a very lightweight but powerful state machine hook for React applications. Essentially combining useReducer and useEffect, it comes with many advanced features such as entry and exit callbacks, guarded transitions, extended state, etc.

Managing state is one of the most important points in a React application. It is what makes our application usable, and all the information that we need is there. It is the heart of every React application—it determines how each component is going to behave and render for the final user.

The React community is very involved and engaged in always finding new ways of managing the state more efficiently, and there are thousands of libraries available for solving state management in React applications. Many of them use different approaches and are recommended for solving a specific problem.

Lately, the React community is making heavy use of finite state machines. Finite state machines are a computer science concept that helps us to manage our state data efficiently, helping us visualize our state machine and making our application free of unexpected side effects.

State Machines

Humans have been using finite state machines for a long time—a traffic light is a finite state machine, an elevator is a finite state machine, clockwork is a finite state machine, etc.

The usage of finite state machines makes total sense for modern applications. Modern applications handle many different states and transitions, which makes the whole application complex to work after some time.

A finite state machine is a mathematical conceptual model that can contain one or more states. It holds a finite number of states and can only be in one state at a time. It also helps us to visualize better how our whole state machine is working, to improve our debugging experience, to prevent unnecessary side effects and to have strong test coverage.

Finite state machines help us to develop a system that is bug-free and to decrease the chance of creating some unexpected between state. It fits perfectly with the React patterns and solves many state-management problems that we encounter in React.

Lightweight Finite State Machines

It is part of the job for a developer to sometimes try to create your solution using some different tool or library. We face it as some sort of challenge, making it a goal, and at the end of the path, we take it as a learning lesson.

Many developers have tried to create their own finite state machines using different libraries and approaches. It is common to see many developers using the useReducer hook, a built-in hook for React, as a finite state machine. useReducer is a very simple but powerful built-in hook that we can use to manage our state in React applications, but we’re not going to cover it today.

Instead, we’re going to look at useStateMachine—a very lightweight but powerful state machine hook for React applications. It is “actually a thin wrapper around React’s useReducer and useEffect,” according to the documentation.

The useStateMachine library focuses on being a simple but powerful state machine for fast and simple development. It was made specifically for React applications, following the patterns, and it comes with many advanced features such as entry and exit callbacks, guarded transitions, extended state, etc.

To get started with the useStateMachine library, first install it:

yarn add @cassiozen/usestatemachine

Then, all you need to do is import the useStateMachine hook inside the file you want to use:

import useStateMachine from "@cassiozen/usestatemachine";

The useStateMachine hook takes an object as a finite state machine definition. Inside the object, the two required properties are states and initial.

The initial property is the state node that the machine should start. The states property is where you define all the possible finite states that the state machine can be in.

Imagine that we want to create a simple finite state machine for handling an open/close state for a menu. We could create it using some other alternative, but a finite state machine helps us to have more safety, reducing the chance of having unexpected side effects and providing great test coverage.

The states property can have many objects, and each object inside it is a state that our finite state machine can be in. It should be defined with the state name as a key and an object with two possible keys: on (which events this state responds to) and effect (run arbitrary code when entering or exiting this state).

For creating a simple open/close finite state machine, we can only have two possible states: open and close. We’re going to create them inside our finite state machine, and we’re also passing close as our initial state node.

const [state, send] = useStateMachine({
  initial: "close",
  states: {
    open: {
      on: { 
        TOGGLE: {
          target: 'close',
        }
      }
    },
    close: {
      on: { 
        TOGGLE: {
          target: 'open',
        }
      }
    }
  }
});

Another nice feature that the useStateMachine library provides us is guards. Guards are functions that run before actually making the state transition: If the guard returns false, the transition will be denied.

const [state, send] = useStateMachine({
  initial: "close",
  states: {
    open: {
      on: { 
        TOGGLE: {
          target: 'close',
          guard: ({ context, event }) => {
            // You can block the transition from happening here.
          },
        }
      }
    },
    close: {
      on: { 
        TOGGLE: {
          target: 'open',
        }
      }
    }
  }
});

Effects can be triggered when the state machine enters a given state. It works similarly to the useEffect built-in hook. An effect can be triggered when you leave the state as well—all you need to do is return a function from your effect.

const [state, send] = useStateMachine({
  initial: "close",
  states: {
    open: {
      on: { 
        TOGGLE: {
          target: 'close',
        }
      },
      effect({ send, setContext, event, context }) {
        console.log('Menu is open!');
        return () => console.log('We left the "open" state');
      },
    },
    close: {
      on: { 
        TOGGLE: {
          target: 'open',
        }
      }
    }
  }
});

The useStateMachine hook also has support for extended states. An extended state, often called context, is an object where we can represent quantitative data (e.g., arbitrary strings, numbers, objects).

We can use the context for storing any data that we want, and this is what makes a great solution for modern applications. We can render how many times our menu was opened/close, for example.

const [state, send] = useStateMachine({
  initial: "close",
  context: {
    count: 0,
  },
  states: {
    open: {
      on: { 
        TOGGLE: {
          target: 'close',
        }
      },
      effect({ setContext }) {
        setContext(context => ({ count: context.count + 1 }));
      },
    },
    close: {
      on: { 
        TOGGLE: {
          target: 'open',
        }
      },
      effect({ setContext }) {
        setContext(context => ({ count: context.count + 1 }));
      },
    }
  }
});

Lightweight finite state machines can be simple finite state machines that help us to visualize our state, easily debug our code and have great test coverage. These are the great benefits of finite state machines.

Though the useStateMachine is a great finite state machine library, other options can be a good choice in some cases.

The most popular finite state machine library for React applications is XState—not only for React applications specifically, but any JavaScript or TypeScript application. We have a topic on the blog covering how finite state machines work in React—check it out if you’re interested.

Conclusion

Developers are falling in love with finite state machines for modern applications. Many applications have some sort of complex state management inside them and a finite state machine can help to simplify it and make it easier to implement new features.

The usage of finite state machines doesn’t always need to be for something complex. We can use a finite state machine for almost anything and it would still be a great choice. We can use it for simple states or complex ones—the advantages that finite state machines bring will always be the same.


Leonardo Maldonado
About the Author

Leonardo Maldonado

Leonardo is a full-stack developer, working with everything React-related, and loves to write about React and GraphQL to help developers. He also created the 33 JavaScript Concepts.

Related Posts

Comments

Comments are disabled in preview mode.