It seems like you're just unfamiliar with state machines. They're not a "new, weird abstraction", they're a core concept of computer science in general, and it can be argued that most code we write is itself an abstraction on top of state machines; not the other way around.
For more information on state machines and statecharts, I recommend reading this great resource: https://statecharts.dev/
From your examples: https://xstate-catalogue.com/machines/confirmation-dialog
I’ve written a million confirm-dialogs in my lifetime, and that example is one of the most convoluted things I’ve ever seen.
Guys, just because it’s in Computer Science, doesn’t automatically mean it’s a more sophisticated solution. The term for this is over-kill.
However, some things just _are_ state machines. You don’t usually see them in the GUI (except for example wizards), but many parsers can trivially be implemented with state machines.
EDIT: Okay, didn't mean to interpret criticism as negativity; I apologize.
So, in a lot of cases in the UI, we are turning what is very recognizable and traditionally understood code into a new form to fit the state-machine world view in a more 1:1 way.
Again, it’s a trade off. What did we gain from this transformation? I don’t think we get enough back from this trade to structure our code in this new way.
Or to be clear, why would I trade my ability to read a few simple fetch calls that update a simple DOM element for this version? What is the outsized return here?
Just my 2 crypto.
I think using state machines is just a very different way of looking at code, and it does take some mind bending to think that way; but once you do, it's much easier to blend the code together the same way you'll see imperative and functional code blended together these days and not think twice about it.
You're saying that he doesn't know computers? Computers are state machines. Presumably he used one to post his comment.
And you can just write this stuff as basically lots of nested if statements but...
As an actual example, I have a state machine in an app I work on:
1. check if there's a session (if so goto 4). 2. If not go to email input. 3. On submit go to password input. 4. authorized. However: 2 and 3 and some of the time 1 have network requests, so handle those. Password can be entered wrong three times (error message changes accordingly, oh also no. retries are modifiable at admin level), which bumps user back to 2 (error message changes accordingly). User may have turned on PIN security, if so that will be an extra step before allowed into app. They may also have it turned on, but not one set, which is a slightly different flow. And they can turn it off/on from within app. Oh and biometric, same deal as PIN. Also can drop back to username/pword instead of OTP in some circumstances.
That's most stuff I think off top of head. That's a hierarchical state machine written using XState. It's easy to test (UI is seperate from the machine, and I can just plug in API functions for the remote calls). It's relatively easy to read (xstate API produces stuff that reads like a diagram, it's easy to explain what's going on). It's easy to add/remove states
There are other potential uses for in my apps (ex. backing a numeric input made up of multiple inputs for PIN/OTP that needs to track focus state + a few other things), but I've been slightly wary about introducing more xstate code.
I feel like:
- explicit state machines are extremely useful.
- they're very well understood by embedded/game devs, but not so much by web GUI devs...
- ...but the latter have an innate understanding due to much of the things they build often being implicit state machines. And those things are often fine as they are.
- adding an explicit machine greatly increases granular control at the expense of [complex] boilerplate.
- the simple fact that they are so explicit (and that you get one state at a time) means state machines aren't some silver bullet for UI, and they get complicated really fast due to state explosion.
- I think any game dev would be able to explain why you don't just use them for everything, and I find it slightly strange that there's little input from game/embedded devs when these things come up on HN -- maybe I'm just assuming there's some overlap of knowledge when actually there isn't.
- heirarchical state machines and state charts fix the state explosion issue, but then bring with them a more complex API that needs to be learnt.
I'd say, for web/JS, XState is gold standard and it's a fantastic library. Explicit state machines are very useful, but writing ad-hoc machines that do anything useful isn't particularly pleasant in JS; XState, with its serialisable object configuration, is pretty easy to explain.
It has the advantage of being completely agnostic w/r/t front-end library, which I think is critical. I can shove it behind an interface, and if necessary replace it with something else pretty easily. I'm somewhat uneasy about having it directly tied to [in the OP's case] React, because I'm in agreement that it's baking clever, complex code right in (yes, for anyone disagreeing, I am aware state machines are a simplification. So is Redux. So are observables. Hell, so is recursion. Anyway).
My initial defensive impulse to this topic was mostly to take a stand against frontend code that is becoming increasingly obtuse over the years.
Here's hopefully a more apt example: imagine wanting to have a multiple document interface (MDI) that shows a bunch of 'window' like views. Some examples of the idea are react-grid-layout, golden-layout, react-mosaic. Each window can be minimized, maximized, moved, flashed, and closed ... all with flashy animations. You could create a whole bunch of components that allow you to capture all the different states and toggle them in different components, and effectively have the MDI business logic sprinkled all through out YOUR code, not just the 'library' code. Alternatively, you could use a state machine that captures all the states in a single spot and manages that all efficiently and safely, you just call an api to trigger state changes throughout your code.
As some other comments have mentioned, this is a fundamental part of computer science. You can get by without knowing it cause you can write a bunch of ifs and state logic all over the place. If you only know how to use a hammer, ya you could still hammer a screw in. Understanding how different data structures of different patterns fit together provides you new tools to do things in different ways when appropriate. Learn how to manage complexity, not fear it, cause there's nothing dangerous about state machines. In fact, I'd argue that anyone that thinks state machines make things more complex just isn't looking at a large enough scale because state machines should make things less complex by wrapping all the complexity inside them (when used appropriately).
Sporadic business logic/spaghetti code is a problem in any application. State machines will not magically avoid this. In fact, it should be just as susceptible to it. When sphagetti code shows up under a complex architecture like this, you could be in a world of hurt. There won’t be a few if-statements for you to unwrap, but instead a maze of cascading state updates. Another common thing I’ve seen is the granularity of capturing any and all state updates, and then some. It’s very tedious.
Anyway, I’ve been dead wrong about 70% of things in life before, so I’d be happy to look at a non trivial app written with xstate if anyone’s got a repo.
Allow popups to see a live visualization of the statechart.
I am using state machines to write reactive applications: https://brucou.github.io/documentation/
So far I have to say that it is a mixed experience. There is a cost to the abstraction and the indirection (that is fairly well known easy to describe even if we rarely do so due to some self-imposed no-negativity bias or having some interest in the game), and then there are the benefits that are less easy to describe because they depend highly on the nature of the problem that you are addressing.
I implemented a Medium clone application (https://codebase.show/projects/realworld) with state machines. The result is 47Kb (brought down to 39Kb after compiling the machine and other optimizations) vs. 70Kb for the Vue implementation or 160 KB for the React/Redux one (that implementation piles abstraction over abstraction in the form of libraries and pays the corresponding price). I would count that as benefit. Also cf. https://brucou.github.io/documentation/v1/tutorials/index.ht... for the full pitch.
The high-level machine is that one: https://brucou.github.io/documentation/graphs/real-world/rea...
At this level, you can follow the routing of the app. Every route is a compound state. If you open it, you see the details of the behavior. The full machine (post refactoring) is like this: https://brucou.github.io/documentation/graphs/real-world/rea...
So one advantage is that with those graphs, it is easier to onboard new folks arriving to the codebase. They just have to follow arrows to know what the code is doing in response to a series of inputs.
I could continue but with all that said, the Hyperapp implementation of the same application is 27Kb and also fairly simple (even if arguably not as simple) to get into. So there isn't a clear-cut ex-nihilo benefits to state machines here. In fact if you would have implemented the same application as a MPA instead of a SPA, for most, if not all of the pages, using a state machine to model the behavior is simply overkill. Most of the job and value of the machine in my example is to do the client-side routing done in the machine (so no extra library cost).
Anyways the bottom line is the tool is useful but you have to figure for what, and where is the value maximized. The obvious cases have already been figured out.
Types of examples I am not interested in seeing:
- a DropDown list using a state machine (or something similarly simple that’s been solved a million times in a simple way).
Edit:
I just think stuff like this is too weird and dangerous to evangelize, and I’m at my wits end in dealing with simple components that have been over abstracted.