Use an XState Machine with React
Tags: react, xstate, javascript, ux
Published: 2021-05-08 22:21:21

XState gives you the tools to take control over the state of your UI. When you've got it under control, you can build interfaces that provide a predictable and delightful user experience.

Let's look at how to integrate XState into a React app.

There are a bunch of well-constructed XState machines available to directly copy into your project from [XState Catalogue](https://xstate-catalogue.com/). For instance, I can interact with and then grab the [Confirmation Dialog machine](https://xstate-catalogue.com/machines/confirmation-dialog) with the 'Copy' button.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itjuhgoymw1ezln3ei4y.png)

I'll then paste that machine definition into something like `confirmMachine.js`. XState is framework agnostic, so there is nothing about this machine, on its own, that has anything to do with React or Vue or Svelte or whatever. I do want to use this within a React app, so I then need to grab [`@xstate/react`](https://xstate.js.org/docs/packages/xstate-react/). XState's React "bindings" come with a `useMachine` hook.

## An Example

Here is what that will look like.

```javascript
import * as React from "react";
import { useMachine } from "@xstate/react";
import confirmMachine from "./confirmMachine";
import Dialog from "./dialog";

export default function App() {
  const [current, send] = useMachine(confirmMachine);

  return (
    <div className="App">
      <Dialog
        message="Are you sure you want to delete something?"
        {/* other props ... */}
      />
      {/* other stuff */}
    </div>
  )
}
```

The `useMachine` call both interprets and starts up the machine service. This hook gives you two values as an array. The `current` value is everything about the _current_ state of the machine. The `send` is a function for dispatching transitions between machine states.

## The Current State of the Machine

With `current` I can figure out the _current_ state of the machine to determine whether or not I should be showing the dialog. `current.value` will tell me what state the machine is in.

I can also get access to any error message that comes from the machine.

```javascript
import * as React from "react";
import { useMachine } from "@xstate/react";
import confirmMachine from "./confirmMachine";
import Dialog from "./dialog";

export default function App() {
  const [current, send] = useMachine(confirmMachine);

  const showDialog = current.value !== "closed";

  return (
    <div className="App">
      <Dialog
        message="Are you sure you want to delete something?"
        showDialog={showDialog}
        errorMessage={current.context.errorMessage}
      />
      {/* other stuff */}
    </div>
  )
}
```

Notice I check `current.value !== "closed"` to determine whether or not the dialog should be showing.

## Moving Between States with Send

I can now incorporate the `send` function into some handlers so that users can interact with the dialog. I'll create a handler for opening, closing, and confirming the dialog.

```javascript
import * as React from "react";
import { useMachine } from "@xstate/react";
import confirmMachine from "./confirmMachine";
import Dialog from "./dialog";

export default function App() {
  const [current, send] = useMachine(confirmMachine);

  const deleteAction = () => { /* ... */ };

  const showDialog = current.value !== "closed";
  const open = () => {
    send({ type: "OPEN_DIALOG", action: deleteAction });
  };
  const close = () => {
    send("CANCEL");
  };
  const confirm = () => {
    send("CONFIRM");
  };

  return (
    <div className="App">
      <Dialog
        message="Are you sure you want to delete something?"
        handleConfirm={confirm}
        handleClose={close}
        showDialog={showDialog}
        errorMessage={current.context.errorMessage}
      />
      {/* other stuff */}
      <button onClick={open}>Delete Something</button>
    </div>
  )
}
```

The `open` handler when called will transition the machine to `open.idle` using the `OPEN_DIALOG` event. It also includes an `action` which will be called if the dialog is _confirmed_. When triggered, this will cause the `showDialog` value to evaluate to true. This handler is wired up to some element outside of the dialog, in this case a button.

The `close` handler is passed to the dialog. When called this sends the `CANCEL` event to the machine. That will transition the machine back into the `closed` state. This change will cause the `showDialog` value to evaluate back to false. Any user action that should dismiss the dialog will trigger this handler.

Once the dialog is open, the user can _confirm_ the dialog's prompt by clicking a 'Confirm' button. This will call the `confirm` handler which will send the `CONFIRM` event to the machine. When the machine receives this event it will trigger the `action` given on `OPEN_DIALOG`.

## Wrapping Up

There are more details to this specific machine. Depending on whether the action's promise resolves or rejects, the machine will take a different course of action. That's an exercise for the reader or the subject of another post.

At this point, we have explored enough of XState in a React context that you can start using the two together. If you'd like you can start by interacting with and remixing [the codesandbox example I used for this post](https://codesandbox.io/s/happy-ellis-e9j6s?file=/src/App.tsx).

There are a lot of moving parts when getting started with XState, so if you have questions about what was covered here, feel free to drop me a note on [twitter](https://twitter.com/jbrancha).

_If you enjoy my writing, consider [joining my newsletter](https://crafty-builder-6996.ck.page/e169c61186)._

---

<em>Cover photo by <a href="https://unsplash.com/@ballparkbrand?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ball Park Brand</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></em>