DEV Community

Cover image for No, disabling a button is not app logic.

No, disabling a button is not app logic.

David K. ๐ŸŽน on November 13, 2019

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...
Collapse
ย 
felix profile image
Felix Guerin โ€ข

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?

Collapse
ย 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€ โ€ข

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 ๐Ÿฅณ

Collapse
ย 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€ โ€ข

Hold on your the David who wrote xstate! I'm a big fan, trying to get Dyson to adopt this ๐Ÿคž๐Ÿ˜ค

Collapse
ย 
uriklar profile image
Uri Klar โ€ข

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!

Collapse
ย 
uriklar profile image
Uri Klar โ€ข

Ok, so after debugging the sandbox a bit I think I get it...
The cleanup function (that turns canceled into true) only runs when state changes from loading to something else (because it is only returned in the loading state).
So... if we've changed from loading to idle 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)

Collapse
ย 
sapegin profile image
Artem Sapegin โ€ข

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.

Collapse
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

Yes there is, and Reason has done it - reasonml.github.io/reason-react/do...

Collapse
ย 
uriklar profile image
Uri Klar โ€ข

Same question, but regarding the XState example.
What makes the cancelation logic work? Does the onDone function not get invoked if the src promise has resolved but we have since transitioned to a different state?

Collapse
ย 
gmaclennan profile image
Gregor MacLennan โ€ข

I had this question too, and eventually found the answer in the docs

If the state where the invoked promise is active is exited before the promise settles, the result of the promise is discarded.

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.

Collapse
ย 
yuriykulikov profile image
Yuriy Kulikov โ€ข โ€ข Edited

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.

Collapse
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

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.

Collapse
ย 
yuriykulikov profile image
Yuriy Kulikov โ€ข

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.

Thread Thread
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

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.

Thread Thread
ย 
macsikora profile image
Pragmatic Maciej โ€ข โ€ข Edited

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.

Thread Thread
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

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.

Collapse
ย 
savagepixie profile image
SavagePixie โ€ข

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.

Collapse
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

(time to add a cat-themed easter egg to the demo...)

Collapse
ย 
gafemoyano profile image
Felipe Moyano โ€ข

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:

const {loading, data, error} = useQuery()
// data.user = { user:  {id: 1, creditCards: []} 
Then somewhere else the user adds a credit card via useMutation()
// magically data.user is now { user: {id: 1, creditCards: [{id:1}] }
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
ย 
karfau profile image
Christian Bewernitz โ€ข

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.

Collapse
ย 
trjones1 profile image
Tramel Jones โ€ข

Great write-up. Thanks for including the buggy examples to play with too!

Collapse
ย 
mrvaa5eiym profile image
mrVAa5eiym โ€ข

Hi all what about this? am I missing something?

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (state.status === 'loading') {
      return;
    }

    dispatch({ type: ActionType.sentData });

    try {
      await axios.request({
        // some code
      });

      dispatch({ type: ActionType.success });
    } catch (error) {
      dispatch({ type: ActionType.error });
    }
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
ย 
juliang profile image
Julian Garamendy โ€ข โ€ข Edited

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.

Collapse
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

Thanks, fixed!

Collapse
ย 
marbiano profile image
Martin Bavio โ€ข

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.

Collapse
ย 
jamesta696 profile image
Jamie Smith โ€ข

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.

Collapse
ย 
tylerlwsmith profile image
Tyler Smith โ€ข

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.

Collapse
ย 
pavelloz profile image
Paweล‚ Kowalski โ€ข

Great article. Ill keep it in my favs. I have a feeling i will circle back to it every now and then. :)

Collapse
ย 
oscargm profile image
ร“scar Garcia โ€ข

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!

Collapse
ย 
eddyvinck profile image
Eddy Vinck โ€ข

Thanks for the reminder. I really need to look into state machines sometime :)

Collapse
ย 
illourr profile image
Dillon Curry โ€ข

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.

Collapse
ย 
kettanaito profile image
Artem Zakharchenko โ€ข

Great article, David! It's been super useful to read through the analyzis and suggestions. Hope to read more from you in the future!

Collapse
ย 
benbot profile image
Benjamin Botwin โ€ข

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

Collapse
ย 
joaomelo profile image
joรฃo โ€ข

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.

Collapse
ย 
ovchinnikovdev profile image
Konstantin โ€ข

Great job!

Collapse
ย 
daviddalbusco profile image
David Dal Busco โ€ข

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 ๐Ÿถ

Collapse
ย 
cookavich profile image
Paul Cook โ€ข

Good stuff, glad you officially published it :D

Collapse
ย 
nickytonline profile image
Nick Taylor โ€ข

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!

Collapse
ย 
vraa profile image
Veera โ€ข

API first, UI next.

Collapse
ย 
jasonawise profile image
Jason A. Wise โ€ข

This is awesome! Thanks for sharing!

Collapse
ย 
avgcr profile image
AVGCR โ€ข

Well, now we have longer code than it should

Collapse
ย 
davidkpiano profile image
David K. ๐ŸŽน โ€ข

Go ahead, write shorter, buggier code.