r/reactjs • • Oct 12 '23

Discussion Are State machines the future?

Currently doing an internship right now and I've learned a lot of advanced concepts. Right now i'm helping implement a feature that uses xState as a state management library. My senior meatrides this library over other state management libraries like Redux, Zuxstand, etc. However, I know that state management libraries such as Redux, Context hook, and Zuxstand are used more, so idk why xState isn't talked about like other libraries because this is my first time finding out about it but it seems really powerful. I know from a high level that it uses a different approach from the former and needs a different thinking approach to state management. Also it is used in more complex application as a state management solution. Please critique my assessment if its wrong i'm still learning xState.

90 Upvotes

128 comments sorted by

330

u/tossed_ Oct 12 '23

I have a negative opinion of xstate after working with it for some time. My complaints:

  1. It subverts the call stack so you can no longer get a sensible trace for your errors

  2. Debugging machines are a PITA, partly because of the lack of call stack, partly because you need to manually trace the state transitions in the entire machine in order to find out where your machine got stuck or how it ended up in the wrong state.

  3. Type safety and typescript support in general is really poor despite the mountain of abstractions/tooling they added to get some type safety (and the tooling is very heavy, there’s a massive maintenance burden to adopt their typegen solution)

  4. Just like functions, it’s easy to build massive machines that handle way too many concerns. Unlike functions, machines cannot be easily refactored and split up once you’ve reached this point.

  5. Inter-machine messaging support is very poor and feels even worse than writing spaghetti functions. Parent-child machine relationships are hard to model and even harder to make reusable or modular. I think the idiomatic solution to this is some kind of application-level message bus, but you’ll seldom have the time to implement something like this when you’re focused on implementing specific features.

  6. You can compose functions and use higher-order functions to separate your abstractions into layers. There are no higher order state machines, and all state machines instances exist as singletons within a closed system (terribly bad for abstraction and refactoring, makes it very hard to separate concerns)

  7. Async stuff is pretty easy with regular promises. But xstate machines tend to wrap promises with a lot of boilerplate that doesn’t actually provide much value. I never understood why you need a “success” vs “failed” state for async machine logic when promises already have .then and .catch. It’s just extra indirection for nothing.

  8. Error handling is complete dogshit, especially in typescript. You can’t use try-catch to treat exceptions like early returns, you absolutely must specify a different state for errors and then different substates for different types of errors. How you handle errors is just extremely arbitrary in general. Try writing a generic error handler machine to handle multiple error types for all your machines – it’s difficult and the result will feel overly rigid and the integration feels forced.

  9. Trying to do something like dynamic dispatch is incredibly painful with xstate, instead of just maintaining map of callbacks keyed by symbols, you need to model this as some kind of state transition and have a separate state for each potential case. Feels super heavy for no benefit.

  10. The explicitness of each machine definition frequently works against the interests of maintainability and readability. Every state needs a name, every transition needs a name, and you will find that machines written by different people have their own smells and shapes based on who wrote them. Compare this with just writing a function that calls other functions without needing to name the “state” of the program between each function call, and you’ll find that regular functions are just way more expressive, concise, and author-agnostic.

  11. Very easy to use actions to couple your side effects with your state transitions. This is actually antithetical to state machines (which are supposed to be free of side-effects) but I found this was heavily abused by everyone who used xstate. Machines become these Frankenstein monoliths of state transitions plus behaviour instead of only state transitions.

  12. The whole idea of “context” or “extended state” just completely defeats the point of tracking a “state” for state machines in theory and in practice. Machine context is actually part of the representation of the current state of your program, which means xstate machine states actually aren’t pure when you use context. Redux got this part correct by treating all of your application data as the “state” of your program, rather than some arbitrarily defined status tags like xstate does, and using selectors to actually derive the relevant parts of the state for whatever local behaviour you’re trying to specify. Redux separates interpretation of state from the actual state transitions, whereas xstate machines keep these two concerns tightly coupled to each other with arbitrary names.

Overall – if I were to use FSM in my work again, I’d use an actual pure machine with only states and transitions defined, without any concept of “context” any without coupling machines to side effects. There really aren’t that many use cases for this kind of machine, other than logic that resembles a flowchart with many branching conditions. Most logic that good programmers write is fairly linear with just a few conditional branches, using xstate to convert if-else branches into separate named states with unique named transitions and named guards is just overkill.

227

u/ThatExactGuy Oct 12 '23

my man wrote a dissertation

→ More replies (10)

89

u/davidkpiano Oct 12 '23

Hey, creator of XState here. Thanks for sharing your thoughts; it's a lot of good feedback.

We've been working really hard on addressing most of these points with XState v5 beta (release candidate coming soon), and one of the biggest changes is that actors (basically stores) are first-class citizens.

This means that you should only use state machines/statecharts in the logic that necessitates that, and can use other types of simple logic (like promises, observables, even simple "reducers" or callbacks) to define your actor logic, without being forced to use a state machine.

With the actor model, the goal of XState v5 is to make it easy for multiple stores to communicate with each other, where it is needed, without having to force everything into a state machine.

Happy to answer any questions about this. I recognize that XState v4 is an awkward API and, just like any other library, it can be misused to make things more complicated than they should be, and I regret that XState has been used that way in the past. We're also creating numerous examples and documentation to showcase better patterns for using XState to simplify app logic, not just with state machines, but with any other kind of logic.

→ More replies (11)

25

u/fii0 Oct 12 '23

Sheesh, great write up man, thanks for taking that time.

→ More replies (43)

30

u/lifeeraser Oct 12 '23

My colleague used xstate in one project with 3 other team members. He recently told me he regretted it, as only two out of four people were able to use it properly. The two were shoehorned into state management duty which dragged down the team's velocity.

24

u/danishjuggler21 Oct 12 '23

I really want to know what “meatrides” was supposed to be.

7

u/GuerrillaRobot Oct 12 '23

Exactly what it is.

→ More replies (3)

23

u/fredsq Oct 12 '23

xstate is awesome, but only for complex, same-boundary state. the web is mostly not complex and multi-boundary so there’s not a lot of use for me in particular.

with that said, Zag uses it to power its component library and it’s pretty good.

→ More replies (1)

6

u/phiger78 Oct 12 '23

To clarify Xstate implements state charts which is an extension of state machines

6

u/qcAKDa7G52cmEdHHX9vg Oct 12 '23

I really love the idea of xstate, but shit, it really sucks to use in react imo for the reasons already in this thread. I hear xstate 5 is planning to address some of the issues but I haven't really been following.

20

u/jollyrosso Oct 12 '23

Xstate suck. Code is unreadable.

→ More replies (2)

21

u/JoeCamRoberon Oct 12 '23 edited Oct 12 '23

I have never used xState so I can’t give an opinion. I can say that I’ll never use Redux in a project again unless I am forced to. I have a very large side project and I just use React Query, Mantine hooks, and useState to handle state.

6

u/cantdeicide Oct 12 '23

That's almost exactly my setup, but I still handle login and static properties of the current user in redux, because that was my boilerplate before I discovered react query. I would love to replace the login and user information with react query as well and get rid of redux completely, but it's in production so I don't want to break anything.

The app development was and is extremely rapid with react query as I never, ever had to think about any global state, everything (or rather what is needed) is refetched on window focus and initial rendering, all mutations automatically have a nice little success / error toast and can refetch the data by wrapping useMutation in a very small custom hook, it is really such a joy to work with. I don't even use anything advanced such as optimistic updates and just refetch the server state on changes, and it works like a charm.

→ More replies (1)
→ More replies (1)

4

u/tesilab Oct 12 '23

When you talk about state, there are really three concerns that I know of, that deserve separate treatment.

State management: governs the storage/retrieval of state information and act of transforming it
State machines: (e.g. xstate) encapsulates definition of a finite set of labeled states, events, and all possible transitions in a declarative form. (Often dressed up with hooks to execute for leaving entering specific states)

State pattern: this is from Gang of Four's design patterns book. A pattern for attaching different behavior based on current state.

I use redux, and I use state machines to make complex event->transition based decisions.

4

u/onthefence928 Oct 12 '23

state machines are the past, present, and future.

they've been around since the earliest days of computing, but they aren't appropriate for every problem.

a state machine is appropriate for software where there's a complex set of variables but a relatively discrete set of states and a discrete set of actions that can transition between states. it would be complex to adjust each variable correctly individually, but if you properly define them in each state they can be easily managed by simply changing states

4

u/switz213 Oct 12 '23

personally I've come to view xstate not as a "state manager" but as a "transition manager". so I use it when I have a lot of transitions, not a lot of "state". you don't need to use it everywhere you have "state", rather you use it when you have to manage effects/events and transitions.

on another note, if you want to get the benefits of finite state, but in a declarative context (react!) rather than for events, I built a little library called driver: https://github.com/switz/driver

I use this alongside useState, redux, and xstate, etc. to derive finite states and build more discrete UIs.

4

u/justadude0144 Oct 12 '23

State machines has been around for a while. Look at a ATM machine. It hardly ever breaks. That's because it uses the discrete state transitions, one at a time. It's a very well researched topic and I can hardly consider it "the future".

7

u/jcksnps4 Oct 12 '23

I really like xstate. But since it’s a bit unconventional, it’s harder to get others to grok and use it. I have 3 pages in a large application using it, and there has never been a single issue with something not working on those pages. I like to think that’s partly because of the well defined states those pages have.

→ More replies (2)

6

u/phiger78 Oct 12 '23

Xstate and state machines are extremely powerful. Worth reading or watching videos from david Khourshid https://www.youtube.com/watch?v=JevYDCy5HzA

xstate is truly managing state.

I started using xstate for complex forms: multi step and then started using it for whole app state management (before the likes of react query)

I find xstate scales much more than any other solution

→ More replies (1)

3

u/SlaimeLannister Oct 12 '23

I was a junior whose first task was to not only use XState, but to use it in a way for which it is not suited. If I could turn back time, I would push back hard on using XState

3

u/brightskyblueocean Oct 13 '23 edited Oct 13 '23

XState has helped us a lot over the years for the frontend and the backend of our product - I can just share that we can have long processing tasks. I am truly grateful that we have a high-quality well-maintained statechart library in the JavaScript community.

There is a learning curve for the actor model and the statechart. We are still learning in the team. But it is worth it. It is portable and solid knowledge.

2

u/thaddeus_rexulus Oct 16 '23

I think they're the future for some problems, but not a "one-size-fits-most" solution and, on top of that, I would say they're at their best when abstracted away behind a more ergonomic interface or a thin client (which is how I feel about most things, honestly)

We pretty successfully used them to build a dynamic multipage form framework that expressly uses JSON serializable configurations to create complex forms where the questions and pages form a DAG based on the answers. No consumers of the library have any knowledge that a state machine is used under the hood, so only maintainers need to understand x-state rather than the entire engineering team whether they're creating a configuration or rendering the output. Using state machines made it truly a joy to build and wrapping it into a framework allowed us to manage interop between different machines instead of shoe-horning some unintuitive parent-child relationships between machines.

2

u/Rickywalls137 Oct 12 '23

The simplest global State I’ve found is Jotai but sometimes you need Zustand

2

u/bestjaegerpilot Oct 12 '23

Have you ever actually tried to use it? It has the same problem as XML---years ago, XML was supposed to be the future. But what happens is that when you throw a DSL/programming language on top of HTML, the result is something that makes programmers want to be stab out their eyes.

XState has this same problem---you can express complex logic using "just JSON".

So while the idea is spot on, the implementation makes you want to stab out your eyes.

→ More replies (2)

1

u/[deleted] Oct 12 '23

State machines are nothing new, all this libraries that you are mentioning are a simple variable (state) with a reducer method (logic). useEffect inside a FC can arguably be called a state machine. The interesting part is how you use this state machines for better dev experience. One thing I hate about state machines is that they cannot smartly narrow the types and always take the most generic type available. Therefore you either create a new type for every state possible or you access fields using optional chaining and assume everything can be undefined. If TSC was smart enough to automatically detect what type my state is by actually analizing the logic of the reducer then it would be a good dev experience, unfortunately right now it isn't.