JavaScript state machines and statecharts
xstate.js.org
xstate.js.org
My personal recent example is a wizard in a web app built as a single major component, with some subcomponents, that goes forward and backwards through 3 different major flows depending on use case. It's been somewhat tamed over time, but we still find cases where an error on step X gets us into an unrecoverable state, sometimes with actual longstanding data impact or where closing & reopening the modal doesnt get the user back on course. With a state machine, I feel, things would be far more predictable.
Having some actual Computer Science in your coding can be a huge boon.
The fact these state machines aren't the mathematical constructs from CS, but "extended" versions with "context" (this is where all the actual app state goes), should be the giveaway that they aren't a sufficiently capable model for the demands of UI programming. After you add extensions for every possible need, what do you end up with?
I understand the appeal though. UI development is a complicated mess, and people want a formula to make sense of it all. I've went through several iterations of these cookie cutter patterns form MVC to MVVMC++ to flux to redux to redux-saga to immutable.js to Recoil and so on. At best these marginally improve the situation, at worst they end up adding complexity on top of the inherent mess.
At the risk of missing out on the One True Way of doing UI development, I'll pass on this latest installment. I keep open the possibility that state machines "marginally improve the situation" though.
Can you give some details on which parts of our docs you feel are "incomprehensible"? I'm curious which specific pages you've been looking at, and for what purpose.
We've tried to organize the docs using the "Documentation System" approach described at [0]: Tutorials for teaching step-by-step, Explanations and How-To guides for specific topics, and References for API details.
Generally we want people to go through our "Redux Essentials" tutorial [1] as the primary way to learn how to use Redux correctly. It teaches "modern Redux" patterns with Redux Toolkit as the standard way to write Redux logic (including RTK Query for handling data fetching), and React-Redux hooks in components.
I'm genuinely interested in feedback on what explanations aren't clear and how we can improve things!
[0] https://documentation.divio.com/
[1] https://redux.js.org/tutorials/essentials/part-1-overview-co...
For most people doing a small CRUD pages that's just woefully overkill.
Simple structure, powerful, what do you want more?
This way, the internal behavior of the machine is still deterministic and easy to reason about.
"State(S) x Event(E) -> Actions (A), State(S')"
"If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'."
The fn needs to yield both the new state and a collection of side effects of that transition that need to be handled somehow. You'll need side effects no matter what so might as well formally describe them in the SM, then handle them outside of it in a way that fits the language/framework/runtime. I think this is basically exactly what elm does and how a couple of the redux extensions work too.
I've worked on MVVM and redux. GUIs end up complicated and difficult to understand if you're not familiar with the codebase.
And before anyone asks: Yes, this comes up surprisingly often. Yes, alternatives have always been explored first. Yes, I'm up to date with browser capabilities. No, you can't say "no" to these designs when you're hired specifically because you can make things work.
https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
> Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
> Every program attempts to expand until it can read mail. Those programs which cannot so expand are replaced by ones which can.
And my own take:
> Every program attempts to expand until it can (a) interpret half of Common Lisp; (b) read and write mail; AND (c) implement a nontrivial subset of Quickbooks Online, including double-entry accounting. Those programs which cannot so expand are eventually forced to thus expand by the mythical force known as the Stakeholder.
One must embrace the inevitability of the "rule.trigger_logic" column, of the "how do we make this work like gmail" question, and of the dreaded interplay between "balance due" and "transaction/account adjustments."
Only then can one find inner peace.
I’ve worked at a concerning number of places now where needing to do waaaay too much with PDF’s has inevitably become a core part of the product.
Cannot for the life of me figure out where I read this.
The advantage of state machines is that you know it doesn't do anything except for what you put in the state transitions.
I get that they're an useful intermediate state for parsers and so, but still. I'm no fan.
Defining the domain-specific abstractions in the terminology of state machines may make them more approachable also to non-domain experts.
this is so, so underrated... i could get pretty flame-y in defense of this point, but a lot of said flame, admittedly, "doth protest too much", as someone trained to (allegedly) be a computer "scientist". but what i will say is that if i had a dollar for every time that software i rely on breaks and/or sucks because there is not enough "science"... there would be a lot more software that actually "works" (tm) in the world (xD).
> Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
Yes, you're right, FSM are terrific modeling wizards or complex autocomplete inputs.
That being said, you don't need full blown FSMs, you can often get away simply modeling and leveraging a type system for this purpose and only allow state transitions to happen through specific functions.
Essentially, the case you describe can be well modeled by a reducer-based redux-like flow with actions that lead to precise mutations.
One thing that I've done in the past was to use xState to model the transitions but then not use it in the actual implementation.
For example, what happens when an enemy gets shot when they are in the ‘dead’ state?
Thinking about 'remove default transitions' - are you talking about using a particular state machine library? My default model is writing them. It's kind of like using a feature flag library when a hashtable with features for keys and boolean values will do. OK maybe use a library if the library adds realtime control etc, but not for the basic concept of flags - likewise a state variable and a single transition function work very well for state machines.
https://mitadmissions.org/blogs/entry/designing-a-calculator...
FSM don't help with the bugs. You need analysis on top to actually make a dent in the bug count
If the state needs to persist across devices it it server side (though a frontend cache can make sense), eg: onboarding flow or "task wizard" state.
Some UI interactions are much easier with state machines, but persisting state isn't a concern even across page refreshes, eg: complex animations or complex form validation states (especially validation that involves time or websockety things).
fwiw, if xstate feels like too much for whatever reason, https://thisrobot.life has seemed like a decent alternative to me.
hmm… seaside… C side… has this been used for anything yet?
Pardon the unnecessary caps, I am using voice dictation.
I created a JavaScript state machine library using them to define each state. https://github.com/JavaScriptRegenerated/yieldmachine
I imagine your approach must be different?
The main drawback with this approach is that it is easy to blow the stack, but it is not too hard to implement trampolining on top of it (by yielding the continuation to a wrapper generator)
To use the generator I send its inputs using `.next()` and obtain current state information, as shown on your library webpage.
They wanted a more senior engineer to join so as to help untangle some of the complications and lead the juniors (that built the thing already? Unclear) out of some kind of rats nest they'd been coded into. I never saw the code so I'm not exactly sure what this meant.
Anyway the founder asked me to look into this xstate platform before our call and asked a lot of questions about my understanding of state machines. The founder had this idea that if the team built out a bunch of state charts using this product and like, diagrammed their entire app, that was their ticket out of whatever sticky situation they were in.
I was pretty skeptical at the time and remain so now. This seems like an interesting analysis method, and implementing SPAs in a state machine way (each component being a state machine) is alright I guess, but, especially for a startup, building out these charts feels like a waste of time. To me it seems like fielding early customer feedback and driving features is more important.
My guess is that the engineers had a pretty good idea what was going on but were struggling to explain it in a way the founder understood, or maybe were slowing down on feature implementation, I don't really know, but I'm always so skeptical of slapping more tools and plugins and diagrams onto the stack of things the engineering team has to worry about.
I'm curious if anyone has been on a team in a situation as I've vaguely described here and found state charts useful in getting them out of a hole?
The project uses password auth and websockets, and I was starting to get bugs around refreshing things that I couldn't easily fix.
I had implemented this originally using a singleton pattern for an AuthService and a MessageBus for the websockets. I created those at app start and put them in a React Context.
Converting things to xstate is working great, but it's nearly a rewrite. The approach I had stashed state in a couple different places and had a bunch of callbacks passed around. Now all that state is just in the state machine's context, and what were methods turn into pure functions.
One thing that's a little non obvious is that if you have async operations (ie waiting for a new token) you probably want a state for waiting for that, where you invoke the async method followed by transitions on success and failure. The docs reflect this, but it's not narratively obvious.
Anyway, liking it so far. Changing to a state machine is going to hurt if your code already sucks.
The last major react project I worked on I found that RTK kinda functioned as a state machine definition for the project, and let us work on requests states in our components, i.e. if loading do this, if not yet loaded do that, etc.
I guess with this state machine framework you'd also be managing DOM state in one spot which could be kind of nice. In the big project we had to consider Formik managed input fields, buttons, modals, etc.
I'm going to do my best @davidkpiano impression and point out that "Redux, as typically used, is _half_ a state machine" [0] , in that a typical reducer responds to actions regardless of what the current state value might be, whereas a true Finite State Machine always checks the current state first before deciding if the current event is relevant.
You _can_ absolutely write Redux reducers as true state machines. With `createSlice`, you'd generally need to have a field in the slice that's some kind of enum, and check that first inside of a reducer:
const someSlice = createSlice({
name,
initialState: {status: "x", someData},
reducers: {
somethingHappened(state, action) {
if(state.status === "x") {
// _now_ consider if the "somethingHappened" action is relevant
}
}
}
})
Very doable, but not the most ideal syntax, since `createSlice` is focused on "here's an action / thing that happened, here's the reducer that handles that".On the flip side, you can also use XState state machines as Redux reducers. A state machine is, after all, a function that takes a current state value + some event, and returns a new state.... exactly the same as a reducer function!
David and I have been saying for a while that we'd like to have a more official integration between XState and Redux. A while back, Matt Pocock put together an proof of concept for what a `createXStateSlice` might look like [1]. I actually sat down with David a couple weeks ago and we did some further design discussions about the possibility of using the `@xstate/fsm` package (a smaller version of XState's logic) as a starting point, and generating RTK actions based on that. No code yet, but it seems feasible.
[0] https://dev.to/davidkpiano/redux-is-half-of-a-pattern-1-2-1h...
I hadn't even thought of the `reducers` slice property. I meant more all the thunky features around the `endpoints` property and the hooks that are autogenerated as a result. In our components this meant we could have for example a form that takes an api model as input and can modify that model on submit. So like,
...component boilerplate
const {
data,
isLoading,
isFetching,
isError,
isSuccess,
} = useGetSomeDataQuery();
const [updateData, {
isLoading,
isUninitialized,
isSuccess,
isError
}] = useSomeDataMutation();
And then right there is all the state we really need to worry about for a given form as we interact with the API. Though again the missing bits are the stuff managed by Formik, like changed but unsubmitted inputs and etc, but those definitions would also be up top near the RTK stuff and so at least while editing it's visually available. Like you said doesn't necessarily pass muster as an actual state machine unless you have lots of checks in your useEffects or wherever else you're reacting to state changes about the entire state of the component.1. If you go and reverse-engineer an accurate Statecharts model of the current behavior, it will probably be weird, and not exactly what you would've done if you'd been workig form the model. You might also discover problems, but fixing the model might be tricky.
2. You really need buy-in by the engineers (not only the managers), and for them invest enough in learning how how to model systems well this way. (Example from UML, not Statecharts: it seemed almost everyone using UML tools was only visualizing their class inheritance hierarchy, and only in a management-slides kind of way. Which I'd say is at most 1% of the benefit.)
3. If you start working from models, the code and model had better agree.
(Background: I made a pretty complete Statecharts DSL compiler for Java, circa '97, and previously worked on various tools and methods for purposes like you describe.)
So if you are asking "will state charts make my software more reliable" => then the industry response is a large renouncing yes. The unanimous opinion is that they are the foundation of systems analysis and reliability.
I think it would be a good day when someone builds a React front-end for Simulink Stateflow that exports JS the same way we export C for automotive.
Stepping past that, which they shouldn't, maybe in that gap, the founder picked a tool, perhaps prematurely. They also sought out a more experienced engineer to bridge the gap. Who knows if the engineering team even noticed the gap and attempted to improve communications somehow.
I have in practice found state machines to be best at adding foundations and a mental model for everyone to share. They are bad at making people think they can get simple flow charts of their business processes. The reality has always been, the real world is complex and regular code is already the best abstraction for dealing with it, especially when someone (founder) wants it to be more simple than it actually is.
I worked in a team that modeled with state machines and it was useful but I was the most junior member at 7 years experience
Onboarding new engineers to it can be quite a chore, especially if they're not used to thinking about different execution paradigms. That said, the ability to autogenerate visualizations of the state graph is super near, and there's so many nice utilities inside of it that make life easy once you get going.
Sadly this is what I've run into as well. Having built a serious application from the ground up with XState at the core of the architecture, it's the closest thing to code nirvana I've ever reached. But getting buy-in across the org was an impossible task due to the overhead and boilerplate.
BUT.
Once a state diagram saved my team a whole lot of pain. We were translating a piece of regulation to code. There were a bunch of people directly talking to us that had a little idea (in that case, worse than no idea) of what the persistence of the real system was.
The diagram made wonders: I was able to ask "is this situation the same as this? no? why?" and actually make myself understood when I said "that won't work" instead of getting in a loop.
Best of all: non-technical people got really well what the diagram meant and, after a while playing with it, they liked it.
Big fan.
[1] - https://kentcdodds.com/blog/implementing-a-simple-state-mach...
I’m curious if others have found it worthwhile for more typical web apps too. My instinct is it’s usually overkill.
https://workflowengine.io/blog/why-developers-never-use-stat...
On the other hand, programming in state machines makes more sense when you're dealing with network protocols like TCP or distributed systems like Raft (both the implementation of Raft and the clients of Raft are modelled as state machines).
It's hard for me to explain why state machines feel like overkill/overengineering in application development but feel natural in network and distributed systems programming.
For example something I've seen a few times is a db table with the fields "account confirmed bool" "account_deleted bool" and "account_suspended bool." There's a state machine in that app somewhere, even if they wouldn't describe it that way.
I think developers having better awareness of when they're drifting into this approach and intentionally choosing it or not would be a much better situation.
As I see it, state machines are a json serializable representation of logic.
We also have json schema for representations of valid inputs. GraphQL or similar as representations for fetching data. And of course jsx for rendering views.
Put all these as a Venn diagram, what are the overlaps?
Can we use these constraints as a way to generate robust and consistent apps?
https://docs.viewflow.io/bpmn/index.html :
> Unlike Finite state machine based workflow, BPMN allows parallel task execution, and suitable to model real person collaboration patterns.
BPMN: Business Process Model and Notation: https://en.wikipedia.org/wiki/Business_Process_Model_and_Not...
On the difference between the map and the territory.
> - Can TLA+ find side-channels (which bypass all software memory protection features other than encryption-in-RAM)?
From https://news.ycombinator.com/item?id=31617335 :
>> Can there still be side channel attacks in formally verified systems? Can e.g. TLA+ help with that at all?
From https://news.ycombinator.com/item?id=33563857 :
> - TLA+ Model checker https://en.wikipedia.org/wiki/TLA%2B#Model_checker :
>> The TLC model checker builds a finite state model of TLA+ specifications for checking invariance properties
Not sure what TLA+ has to do with either.