DEV Community

Mikey Stengel
Mikey Stengel

Posted on • Updated on

State machine advent: Invoking a state machine in React (5/24)

After having created an abstract state machine that models the behavior of our application, it is now time to wire the machine to a React component. Our invoked state machine (also known as service) should dictate the current state of our component at all times.

If you want to jump right into the CodeSandbox, feel free to do so. Below you can find all the relevant pieces of code with a brief explanation.

Here is the light switch machine we've created again.

import { Machine } from 'xstate';

const lightSwitchMachine = Machine({
  id: 'lightSwitch',
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: 'active'
      }
    },
    active: {
      on: {
        TOGGLE: 'inactive'
      }
    },
  }
});
Enter fullscreen mode Exit fullscreen mode

If we want to use the machine within a React component, we need to install a tiny package called @xstate/react. It exports a couple of hooks with which we can invoke state machines and friends.

import React from 'react';
import { useMachine } from '@xstate/react';
import Switch from 'react-switch';

const LightSwitch = () => {
  const [state, send] = useMachine(lightSwitchMachine);

  return (
    <Switch
      onChange={() => send({ type: 'TOGGLE' })}
      checked={state.matches('active')}
      aria-label='Toggle me'
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

Don't mind the imported <Switch /> component. We are merely using it so that we don't have to write custom CSS to get a decent looking switch.
Instead, I want to focus on the useMachine hook. You can find its full API specification here. It takes in our machine as an argument and returns an array. The first entry is the state object of our invoked machine. It is not a string value telling us whether the service is in the active or inactive state. It's an object with a lot of properties to access the internals of our invoked state machine. Very rarely, we'll be interested in anything other than state.matches.


As an aside, if you don't want to worry about the other properties, you could totally just destructure the one you'll need.

// array destructering, object destructering and aliasing to isStateMatching 
const [{matches: isStateMatching}, send] = useMachine(lightSwitchMachine);

// then use it like this
isStateMatching('active') 
Enter fullscreen mode Exit fullscreen mode

Going back to our example from above, matches() is a function that returns a boolean. Use it to assert whether the current state of our service, matches the state node passed as a first argument.

checked={state.matches('active')}

In plain English, this line is saying: If our invoked machine is in the active state, pass true for the checked prop of our Switch component. If our machine is in any other state, pass false.

The last thing that we need to cover for today is the send function. If you've used the useReducer hook or Redux in the past, you might be used to calling this function dispatch. Even though you should be able to ditch Redux at the end of the series, you can continue to use the name dispatch if it works for you.

As the name indicates, it is used to send events to the invoked machine which can react to the event by transitioning from one state to another. There are two ways to send events.

You can either pass an object to the send function and set the event name as a value of the type key. This is how I used it in the example above send({type: 'TOGGLE'}), or you can just pass the event name as a string value send('TOGGLE').

I hope this post gave you a brief overview on how to invoke an abstract machine and use the service within a React component. I'm very excited about the post tomorrow as we'll solidify our learnings from today by looking at the differences between an implicit and explicit state machine.

About this series

Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.

The first couple of days will be spent on the fundamentals before we'll progress to more advanced concepts.

Discussion (2)

Collapse
karfau profile image
Christian Bewernitz

Thank you for writing this, I'm enjoying reading those small chunks.

But in this post I was surprised: For simplicity and less future code changes I would suggest to not rename the destrctured matches function.
Also everybody knowing the API of StateX will recognize the method and know what the code means, without checking where this custom name comes from.

In a simple example like yours it's of course not such a big issue, but if this becomes a habit or the code lives (and grows) longer then expected, as we all know it does most of the time, keeping it simple helps.

Thx again for and keep up the good writing!

Collapse
codingdive profile image
Mikey Stengel Author

Yes you are right. I'd never destructure it in my code personally but thought I'd mention it anyway as it might be easier for some beginners to just deal with the one important function than to use the state object and possibly be confused by all the other stuff that they'll see in their editor once they type state.

Thank you for your feedback :)