How to Effortlessly Model Async (React) with xstate’s Invoke

Matthew Jones
7 min readOct 30, 2019

--

Just want some code? This repo shows how to integrate xstate with React Context + Hooks. However, there isn’t any invoke code, so it’s worth reading on!

Some Context

In the previous post to this now non-existent two-part series, I concocted this idea of “guard actions,” and the idea behind that was essentially in the real world I needed some kind of way to fire off xstate actions asynchronously and sequentially, and also be able to recover from errors.

At that time, xstate didn’t have the capacity to handle firing off an array of async functions such that if there was an error, the state machine could transition back to the state it started from, and that was essentially what the last post was about.

I wrote a hacky generator function that would allow you to do this “go-back” transitioning for errors, but ultimately that approach isn’t optimal because it doesn’t really think “state machine.” I’d say despite trying to make it more universal, I have a feeling it mostly just solved my problem. So let’s cancel that.

What I want to talk about in what would have been Part Two of this series is the invoke property that was introduced to xstate since the time of writing Part One, because invoke-ing is so much more powerful than the generator function I was using previously. It’s idiomatic. It thinks in “state machine.”

It’s crazy good.

Instead of asking you to worry about guarding your current state or anything like that, it instead realizes the fact that promises are themselves state machines (they have an initial, failed, and success state). So the use case I was exploring in Part One — handling asynchronous error states and recovering — is actually solved by invoking/nesting, as opposed to “guard actions” or anything like that.

So if you wan to learn how to use xstate to easily model asynchronous logic in your real-world applications, that is exactly what I am going to talk about now.

But first let me shift into hype mode because whereas I loved state machines before, with the invoke property I feel like I have ✨ super powers ✨ when it comes to how I now model application state. I’ve used used Recompose, Redux, Observables, MobX, “regular” Context + Hooks—pretty much everything out there, and for me at least, state charts are not just the most effective, but also the most fun.

Yes I have fun writing state charts. Stop looking at me like that.

Ok enough hype, let’s write some code!

Getting Acquainted

The xstate docs are really good, so I don’t really want to re-create the wheel here (and I’m somewhat assuming you have at least a cursory understanding of state machines), but what I will do is copy over a subset of the invoke API, with the example from the docs:

API

  • src
  • onDone
  • onError
  • data

Example

example from the xstate docs

So here we see the fractal nature of how state charts excel: the invoke key on loading is could be a promise, or another state chart that houses a bunch of other promises.

This is super powerful.

Not shown in the picture is the onError key. The machine will obey the state instructions found in the the onError key if an uncaught error bubbles up beyond the scope of your promise. In other words, automatic error handling for promises. In the event that someSrc is another state machine, then onError would be called if the final state for that machine was error.

Error being “first class citizens” in your state machine, as opposed to some afterthought for a product feature, will make you a better UI engineer.

Assuming the promise/chart doesn’t error out, when someSrc completes, it can move to loadBar, which might do some more async work with its own invoke, before finally moving into loadingComplete.

If you are wondering what the data attribute does, that is what allows you to pass information between nested machines that are calling up to their parents.

It’s structured, sequential async work. And it’s ridiculously easy. It’s easy to create the initial structure, and it’s easy to add in states here-and-there when you are iterating.

You (can) keep nesting until you have properly addressed the complexity of the situation. Liam Neeson voice: there’s no complexity that doesn’t eventually fall before a fractal.

A Rule of Thumb

Typically, what I will do is model my promise states by how I want to catch errors.

If you are comfortable with three promises all firing inside a single catch , then those promises can all reside inside one loadFoo ‘s invoke. If you want to fire three promises, and catch errors on each one, and potentially take different actions for each one, then they should be their own invokes on their own state nodes.

Pretty common sense. I don’t know if the xstate docs explicitly mention doing this, but I feel like it’s kind of implicit to how state charts and javascript error bubbling works.

Testing

The way the xstate API works, is the invoke key can take a direct reference to a promise/state chart, or it can take a string reference that gets looked up in the services key in the options object.

This means that you can supply your own mocked promises to the state chart for testing.

From the xstate docs.

Key thing here is that we’re augmenting the machine services using withConfig to change getUser to our mocked testing function.

The ability to structure state, and then easily mock it up for testing is imo one of the more overlooked areas of state charts. I know that the author, David has a testing philosophy that espouses trying to generate tests as opposed to write them, and this is a great way to realize that ideal.

There is also an @xstate/test package with a strategy that allows for putting testing information into a test key within the state chart. I haven’t used that yet, so I can’t really speak to it, but it looks interesting.

Working With React

All right, here’s a basic data-loading React component. The UI is more-or-less a function of state, as React should be. Notice how close this is to the upcoming Suspense API.

A few things to note here:

  • Context is essentially state for xstate — kind of confusing but yeah, it’s where you track machine state. Fetched data, computed data, that sort of thing.
  • Send is similar to Redux’s dispatch, where you fire a string action that corresponds to the action on the state chart, and the React hook interprets the state change, propagating a re-render.

IMO, keep as much of your state hoisted up into machines as possible, so that if you need to, you can switch rendering frameworks relatively easy. The @xstate/react hook could easily be repurposed to work with other frameworks.

On Using Hooks for State

With the previous app I worked on that used xstate, we definitely used our fare share of useState. Hooks are lightweight and great when used correctly. Sometimes for perf or otherwise you will want to just stick with hooks. That’s fine.

My rule here: if the state of the component with the hooks interacts globally with the app, then it might be a good idea to move it into a machine so it can benefit from the actor model stuff. If the state is completely local to the component (like a form) just leave it.

Final Thoughts

I hope you are starting to get excited about working async with state charts. Who knows when Suspense will drop, so this is a good distraction in the meantime, right? I think so, at least.

Now, here’s the thing. This story isn’t over yet.

With Node and the IoT mostly being driven by Javascript, that means that if you learn xstate (or any other similar lib) you can model your states anywhere.

If there’s interest, we’ll take a look at some of other other non-frontend environments in Part Three of this non-series of blog posts. So if you liked this post, be sure to hit a couple claps, and I’ll see you next time!

--

--