u/madskillzelite

2,357 post karma · 3,873 comment karma · joined Jun 2010 · moderator

madskillzelite

Hey, thanks for the feedback. I agree that the composability and actor communication leave a lot to be desired. We're planning XState v6 and working on coming up with good, intuitive solutions for these.

madskillzelite

It's still in beta, but we're building https://stately.ai/editor for this purpose

madskillzelite

@xstate/react has a useSelector() hook that allows you to select values from an interpreted machine:

```js const someSelector = (state) => state.context.count * 2;

// ...

const service = useInterpret(machine); const doubled = useSelector(service, someSelector); ```

This will only rerender when the selected value changes.

madskillzelite

This is awesome! Love that you used XState!

madskillzelite

There are some updates coming to XState to make it even easier to get started with, even without a huge learning curve, so that it doesn't feel like "overkill".

For simple state machines, you can always use @xstate/fsm which is a minimal (and tiny!) version of XState with a much smaller learning curve. And it's still compatible with visualization tools.

madskillzelite

Hmm, you're not exactly right about what a finite state machine is. It's not an object with functions. It's not a specific library. In fact, finite state machines really have nothing to do with syntax or language.

In fact, "Should I use state machines or not?" is the wrong question to ask. You already are using state machines; you're just doing it in an implicit way.

Each "finite state" in a state machine represents a behavior; that is, what can happen in that current "state" (status, mode, whatever you want to call it). For example, you can have two different behaviors: sleeping and awake. Obviously, you'd respond to events differently depending on if you're sleeping or awake; your behavior is different. And you can also transition between sleeping and awake - that's another important part of state machines. The behavior can change, and FSMs make that explicit.

Now consider if you were to avoid using explicit state machines; you might put all logic in one place, which means that instead of having different behaviors for being asleep and awake, you consider both of those to be a single behavior and you use defensive programming (e.g., isSleeping, isAwake, isWakingUp, isFallingAsleep, etc.) to loosely differentiate between the implicit behaviors. Sure, you can have the same logical outcome, but the separation between behaviors isn't explicit, and it becomes more difficult to understand the logic as you add more features and deal with more use-cases.

I wrote an article: You don't need a library for state machines that talks more about how to implement these patterns. You can really code state machines however you'd like, even using old-school Redux switch/case patterns.

I'm glad to see state machines trending more in the front-end world. It's not a new idea; in fact, it might be the oldest idea ever brought to web development. Other areas in tech (game dev, embedded systems, etc.) use state machines heavily, so we're just playing catch-up. State machines are not "in vogue"; they're not a hyped trend; rather, they're a fundamental concept in computer science that every software engineer should know, even if they don't use them directly in their programs.

State machines are here to stay, because they were always here, and we're just now realizing that there's lots of benefits (organization, visualization, preventing impossible states, testing, etc.) to making them explicit rather than implicit.

madskillzelite

This is really great, thanks for sharing!

madskillzelite

You're probably using flat state machines. XState implements statecharts, which extend state machines to include nesting, parallel states, history, invoking other machines, etc.

In fact, it is straightforward to separate your logic to use many machines that communicate with each other rather than one single, large machine.

Redux-Saga can't be visualized, by the way.

madskillzelite

There are so many fundamental misunderstandings about XState (and state machines in general) in this article, but the biggest one I want to address is this:

XState, on the other hand, is designed around side-effects. It’s actually touted as an advantage by XState’s creator. (The fact that Redux doesn’t handle — or need — side effects is a big reason that particular state manager surged in popularity to become the de-facto React state manager solution.)

And there’s really no way to reduce side effects — the whole point of XState is that the change in state triggers an action as a side effect of that state change.

This is completely wrong, and in fact, XState by default is safer and more functionally pure about side-effects than Redux (note: it's very possible to have the same level of side-effect safety in Redux). Here's why.

XState, Redux, and useReducer all operate fundamentally the same way:

state + event = nextState

(in Redux, events are called "actions", but for ease of comparison, consider them as events)

When it comes to side-effects, useReducer doesn't handle them at all. Instead, you have to depend on useEffect to trigger side-effects based on state changes. In that way, it feels like side-effects are somewhat of an after-thought; instead of knowing which side-effects will execute based on state changes, you have to declare those separately.

With Redux, the side-effect story is even more fragile, because you have to use middleware to define and execute them. With redux-thunk, they're not based on state, but rather events alone, so you need to defensively program against the state from getState() to make sure they execute only when intended.

So how can side-effects become more robust? If, say, instead of the state looking like:

js { status: 'loading' }

It looked like this:

js { status: 'loading', effect: { type: 'fetchUser', id: '42' } }

Then, as a developer, I know exactly which effect is to be executed with any given state! Then my effect management becomes a lot less complex. In fact, this is already an "accepted" solution in the Redux community by way of libraries like redux-loop and redux-saga by way of declarative effects.

All XState does is build in this notion of "declarative effects" by default. The machine.transition(state, event) function returns a state that looks like this:

js { value: 'loading', context: { ... }, actions: [ { type: 'fetchUser', id: '42' } ], // ... }

And it is up to an interpreter to actually execute the effects declared in actions. But if you just use machine.transition(...), it is a completely pure function.

I write more about this in my post No, disabling a button is not app logic if you're interested in exploring these ideas in more detail (with actual examples instead of just rambling thoughts).

madskillzelite

I think you're mistaking some key concepts - XState manages state changes via events, which would be analogous to Redux's actions (poorly-named). In XState, actions refer to effects, which are absolutely necessary in any application - an application without effects is completely useless! (Simple example: outputting something to the screen, logging to console, making a fetch request, etc. are examples of effects, or "actions" in state machine terminology).

You can't avoid effects, and Redux has no built-in way to manage effects.

Would love to discuss this with you more if interested - my DMs are open.

madskillzelite
madskillzelite

Hey, contributor here.

This article (and the comments) are all great feedback, and we will be using it to improve XState and related libraries (such as @xstate/react).

We're getting ready to release the next version of @xstate/react which will have support for selectors, better compatibility with concurrent mode, ability to specify actions asEffect or asLayoutEffect, and fix a bunch of bugs.

XState v5 is also in the works, which will improve the actor model story with XState, have strict conformance with SCXML, and be smaller/faster in general, with some nice new features.

I'm also working on a purely actor-model library called XActor, which will be the foundation for XState v5, and also a viable, standalone choice for managing complex state.

With that said, please don't blindly use XState on any project without considering whether you need to or not. In general, XState should be used if:

  • You need to manage state that can come from many different sources
  • The behavior of various parts of your app changes over time, and you want to clearly reason about what can/can't happen without littering your app with if-statements everywhere
  • There are a lot of events and event-driven behaviors in your app
  • Your app can be represented as being in many different "modes", or finite states
  • You have lots of side-effects which must be carefully coordinated

In general, if you have a simple CRUD app, it might not make sense to use XState. I would start with useReducer and useContext and work from there. You'll know if your app needs to use a state orchestration solution like XState.

Also, I just released the XState inspector on statecharts.io which should make visualizing the machines easier, so please give it a try!

madskillzelite

What was gnarly about it? (Contributor here)

madskillzelite

It should be typed as `(event: YourEvent) => void`

madskillzelite

You mean this?

// ... loading: { invoke: { src: () => somePromise, onDone: 'success', onError: 'failure' } } // ...

madskillzelite

There's plenty of non-trivial uses of XState, just look around!

All the functionality of XState comes from the SCXML spec, so I understand how it can be a little overwhelming. You don't need half of the features in most apps, to be honest.

Have you seen the cheatsheet for state machines? It helps clarify a lot of the concepts: https://twitter.com/DavidKPiano/status/1245024907131531270

And where are you seeing the boilerplate? In the config object?

madskillzelite

Amazing description!

madskillzelite

Completely agree. And yes, the examples are overengineered. I'm going to be publishing an article teaching how to do more involved drag-drop interactions using state machines, which should hopefully be a better example.

I'm open to suggestions as well!

madskillzelite

How would you make it more friendly? We're working on V5, which includes an alternative builder syntax that you might like.

madskillzelite

Hey, author of XState here!

There's a high push for xState lately on Twitter which is beyond ridiculous and none of the examples provided isn't easier to represent and manage with tagged unions.

You're right; tagged (discriminated) unions and enums can get you pretty far, but there's more to it than that - you also want to be able to prevent unwanted transitions. You still don't need a library for that.

I like finite state machines, but they are severely misusing it and shilling Piano's library without ever providing compelling reasons to use them.

Good! To be fair, I will always encourage you to use state machine patterns before reaching for XState.

It should also be noted that mastering xState is not in the redux difficulty tier, but RxJS or fp-ts tier.

Yes, it's a high learning curve, but that is the nature of state machines and statecharts, not arbitrary decisions by the library itself.

madskillzelite

I am (author here) but Leigh's videos are fantastic and I'd recommend them.

I will be working on a Frontend Master's course soon!

madskillzelite

Tell that to the many companies successfully using state machines (and XState) in production.

madskillzelite

Should I finish part 2, or am I going the wrong direction writing these articles?

madskillzelite

> being strict about ideology leads to inefficient systems

Being too loose ("flexible enough") and less rigid is what leads to introducing edge-cases in your application. That's what I'm trying to prevent.

And I didn't even mention XState in the article, hah.

madskillzelite

Hey, author of the article (David) here!

> Not all requests need to be cancelable

Okay, but if the suggested solution makes it very difficult or impossible... it needs re-evaluating.

> He also said that "Redux isn't a pattern".

My purpose for writing the article is that the typical pattern in which developers use Redux (okay, it is a "pattern") is not the most robust pattern, which leads to...

> ...he has a tendency to dismiss anything that isn't "just a state machine" as not being useful.

No, I have a tendency to dismiss solutions that make it too easy to introduce edge-cases in applications. Can you use Redux with the "normal" pattern and avoid edge-cases? Sure, if you're a diligent developer that ensures all of the if-else statements are in there. It's not going to be very organized code, though.

> As with everything, it's important to understand what problem you're actually trying to solve, and figure out what tool is the most appropriate solution for your specific problem.

Absolutely agree. Which is why I think developers should stop blindly reaching for Redux. Try hooks first, and you might eventually realize you don't need Redux after all. Many developers have.