I'm going to start this post with an excerpt from the book "Constructing the User Interface with Statecharts", written by Ian Horrocks in 1999:
U...
For further actions, you may consider blocking this person and/or reporting abuse
Thank you so much for that article, it's a whole new way of looking at app logic for me and I really learned a lot!
I'm not exactly sure how I would replicate
useEffect()
outside of React and without using Xstate (or any other state machine library). Do you know of a framework/library agnostic way of doing this?That's so funny, today I was to tinkering with xstate on its own and although the title is not talking about finite state machines I tapped to take a peek. I started seeing the switch and scrolled down to suggest xstate... Oh damn haha, anyway nice post 🥳
Hold on your the David who wrote xstate! I'm a big fan, trying to get Dyson to adopt this 🤞😤
Hi David, Thanks a lot for this write up.
One thing that wasn't very clear to me is the cancelling logic in the reducer example.
The cleanup function is inside the
if (state.status === "loading")
block. So how is it still being invoked when status changes to "idle"? (due to a cancel event)
How does the cleanup variable persist across renders?
In general i'd love a few words on cancelation logic since it doesn't look very trivial.
Thanks again!
Ok, so after debugging the sandbox a bit I think I get it...
The cleanup function (that turns
canceled
intotrue
) only runs when state changes fromloading
to something else (because it is only returned in the loading state).So... if we've changed from
loading
toidle
before the promise has returned, when it returns the canceled flag will be true and it will return without doing anything.I do however feel that this logic kind of goes against what this entire post is trying to advocate: declarative, easy to understand logic.
I'm wondering if maybe there's a more "state machiney" way to implement this functionality (without going full on state machine like in the last example)
I also stumbled over this example and agree that explicit cancelation would make the app logic easier to understand. Implicit cancelation feels too close to the
isLoading
from the very first example.Yes there is, and Reason has done it - reasonml.github.io/reason-react/do...
Same question, but regarding the XState example.
What makes the cancelation logic work? Does the
onDone
function not get invoked if thesrc
promise has resolved but we have since transitioned to a different state?I had this question too, and eventually found the answer in the docs
The
invoke
property seems like a little bit of "magic" in XState and it took me a while to understand what is actually happening there.David, thanks for writing this, having a concrete example really helped understand XState and I look forward to seeing more.
Thanks for the article David, it was very well thought out. I was wondering how this approach would work when using something like Apollo's useQuery hook to fetch data.
My initial approach was to assume my component would start on a 'loading' state. It might not be necessarily true, but it seems to work since the first pass of the render cycling useQuery will return a loading value set to true.
useQuery provides a prop for an onComplete function, so that seemed like a good place to call dispatch({type: "RESOLVE", data}) and let the reducer do some work and put the data into the state.
And this seemed to work fine for the most part. However, I bumped into a problem when some other component updated data via mutation. Turns out that onComplete will, understandably, only run the first time the query is completed. But apollo apparently does some magic to notify data that something mutated it, updates it, and triggers a render.
The example goes something like this:
You get a user and its credit cards from use query:
So even though I could send the newly added credit card on a dispatch call, and update the state accordingly, it kind of feels like i'd be maintining two sources of truth. Whatever apollo's useQuery returns and what I've manually placed on the Store.
Anyways, all of this is to say... how would you make this work with Apollo? Are the approaches at odds, or am I making the wrong kind of assumptions on how to handle the response?
Cheers, and thanks again for writing this up.
We also had this question recently and decided to decouple queries and mutations from the state machines where possible.
In the end the appollo hooks implement their own "state machines" in a way.
It's an ongoing process to convert the existing code, but we are convinced that it's the right approach for us.
This is an amazing article! Very well written and loads of food for thought. If nothing else comes out of this, at least you've helped me finally grasp what redux is trying to accomplish. So thanks for that.
My only criticism is that your app fetches dog photos instead of cat photos.
(time to add a cat-themed easter egg to the demo...)
Please never use state machines for UI. It is a terrible idea proven to me by 3 big projects (1-10 million LOC) which have done that.
Dog fetching issue can be solved with RxJS switchMap.
Lol you're already using state machines. RxJS operators and observables are state machines.
The state explosion problem is solved with hierarchical states, which XState supports.
Not everything stateful is a state machine. I all for hierarchical state machines (actually I use them in most of my projects and I even have an open source library for HFSM). But it is not applicable for every task at hand. User Interface is one thing which rarely can be implemented with a SM in a maintainable way.
Tell that to the many developers using state machines in user interfaces already (for years) with great success.
Of course it's not applicable for every use-case, but saying it's rarely useful without evidence is not helpful.
Nobody argues that every app form a state machine. The argue is should it be explicit or implicit one. I am for making not possible state not possible, but I see precise types (sums) as a tool for achieving the most. Runtime FSM looks like overcomplicating the problem.
That's fine, my goal is to get developers thinking about their apps in terms of finite states and preventing impossible states. It's up to you whether you want to use a runtime FSM or not.
Would you mind sharing the highlights of your experience with those three projects that made you realise state machines were a bad idea for UIs?
I have to be careful with the details, so I will only summarize what is already available to the public. I have participated in several projects for major car manufacturers. Three projects were built using a UI framework, which was based on a HFSM. There were a lot of states (a lot!) and the interfaces themselves were quite sofisticated. Many external factors were also taken into account, for example what happens if the car moves and some functionality must be disabled. These projects had from 1 to 10 million LOC in Java just to feed the HFSM with events.
Despite good tooling (visualization), it was an unmaintainable mess. State machine did not actually made the code more maintainable, mostly because state machine was a bad model for the UI. In the end there were clusters of states which were interconnected in every possible way. There were dedicated developers who had to maintain this state machine. I was lucky to participate in another project, which has a very similar user interface (you wouldn't tell the difference), but this time without any tooling or an abstraction for the UI navigation. Well, it was also a mess with your usual "of click here, go there, unless there is disabled, then go elsewhere". But it was actually much, much better. We have experimented with other approaches. I personally find decision trees very practical if the interface is dynamic and has to take a lot of external events into account. And it always makes sense to make user interface hierarchical, for example using nested routing outlets. For simple UI you can get away with a simple backstack.
Thank you so much for writing this!
I've been keeping an eye on xstate, waiting for an opportunity to use it in a real project.
This example of fetching in React was all I needed (so I wouldn't have to figure it out myself)
BTW I think the link to
@xstate/react
is wrong.BRB! I have a buggy application to fix with xstate.
Thanks, fixed!
Hi all what about this? am I missing something?
I'm literally like this right now: 🤯
Thank you for this article, it's amazing how much mental models can influence what we feel like it's good code or not.
This was really cool! I'm gonna need to read this a few more times before I really understand the state machine part, but I have it bookmarked. I'm gonna be working on my most complicated React app I've ever written in three weeks so I should probably start studying up on this.
I just seen a ton of code just to fetch dogs on a click, this can be done so much simpler with less code using native JS.
Mixing JS with HTML seems laughable.
I appreciate your article though.
These frameworks and libraries are getting more bloated by the weeks going by.
Great write-up. Thanks for including the buggy examples to play with too!
Thanks for the reminder. I really need to look into state machines sometime :)
Great article. Ill keep it in my favs. I have a feeling i will circle back to it every now and then. :)
That's the kind of example I was looking for!
I have a similar situation, but with a lot more complexity in which i want to bring state machines and this brings me some light! Thanks!
Nice article David.
You never cleanup the Error message in the state charts example, looks like you need to set error to null on
entry
of the loading state transition.Thank you for the great blog post and clear conclusion 👍
P.S.: The Dog API is my new favorite API for blog post too, such a good API 🐶
Good stuff, glad you officially published it :D
Wow this article blew my mind and introduced me to Xstate.
State machines are used all the time in game dev. I can’t believe I never thought of using them for UI
so beautifully written with advanced concepts presented so pedagogically. i will keep this article in my memory and heart for a long time. thanks and congratulation.
Great post David! I've been reading about XState and saw your stream with Jason Lengstorf. I just need to make something with it. 😉
Side note, but keep up the great work with Keyframers!
Great article, David! It's been super useful to read through the analyzis and suggestions. Hope to read more from you in the future!
Great job!
This is awesome! Thanks for sharing!
API first, UI next.
Well, now we have longer code than it should
Go ahead, write shorter, buggier code.