19 ms·
Using State Machines with React to Make a Confirmation Dialog (2020)
- no_wizard 6y agoThis is what useReducer is for at its core isn’t it? You dispatch named type actions and it executed accordingly. Seems like that would work just as well for this, honestly.
- tshaddox 6y agoA reducer is pretty close to a state machine. The main differences are that state machines generally prevent invalid transitions and have tooling to help you handle and visualize all the states and transitions.
- lewisl9029 6y agoI'm definitely a fan of the concept of using state machines for UI development, but both of the libraries I've tried so far, XState and Robot (or at least their official React bindings), seem to have a bit of an impedance mismatch when used with React: Both of these libraries' React integration involves creating a "service" that accepts events and transitions between states internally, bypassing React's own state management lifecycle, which is tightly integrated with rendering. In the past I've seen a number of nasty bugs pop up when precise synchronization between theses two sources of truth for state is required, and they can be a bit of a nightmare to debug and/or work around. I think a better, more robust integration between state machine implementations and view layers like React with their own mechanisms for state management could potentially involve getting rid of the "service" middle man, and instead delegating internal state transitions of the machine at runtime to React itself, using a mechanism like the dispatch function from useReducer, which seems like a perfect fit. I've been itching to explore this concept a bit more deeply but haven't found the time or motivation between work and various side projects. Would love to see other folks pick up and run with it if they're interested though!
- aphextron 6y ago>Both of these libraries' React integration involves creating a "service" that accepts events and transitions between states internally, bypassing React's own state management lifecycle, which is tightly integrated with rendering. In the past I've seen a number of nasty bugs pop up when precise synchronization between theses two sources of truth for state is required, and they can be a bit of a nightmare to debug and/or work around. I've been using XState and React on a fairly sophisticated large scale application for the last two years. The key is using React hooks with pure components, and eschewing the lifecycle altogether. This turns React basically into just a highly efficient view rendering library, while delegating all of your application logic to the state machine. Your components end up way cleaner and easier to reason about when they are just pure functions without side effects, and all your business logic is contained within a state machine.
- lewisl9029 6y agoI think that's certainly a valid approach, and comes with a ton of benefits. But I don't think that has to be the _only_ viable approach to use a state machine in React without having to worry about subtle synchronization bugs. Especially since even if our endgame is to go all-in on the state machine as our only source of truth for state, we could still benefit from a smooth transition path to get there. Using state machines deliberately for complex flows while delegating simpler state management tasks to simple useState hooks should ideally be just as robust and well supported as modeling our entire UI as a giant state machine. At the end of the day, the state machine definition in both of these libraries is just a bunch of data, to be interpreted by some runtime that's responsible for maintaining internal state and reacting to events. Currently that runtime is a generic stateful "service" implementation provided by these libraries, but there's no reason why that runtime couldn't be some stateful service implementation built on top of React, or any other view layer with its own opinions on state management.
- adamkl 6y ago”Currently that runtime is a generic stateful "service" implementation provided by these libraries” This is how I approached using XState in a proof of concept last year. I created a “service layer” made up of XState machines that handled different high-level aspects of the application behaviour; i.e. not tied specifically to a particular UI element. Things like managing the requests made during an OAuth flow and storing the authenticated user’s information. This gave me a source of application state with well defined behaviours that I could use instead of “redux” and its usual accompaniment of boolean flags and thunks. The best part of embracing the separation of state management from React is that I initially developed the application in Angular before lifting-and-shifting the “service layer” over to React and sticking in a “context”. All the application behaviours remained the same, it was just a matter wiring up some JSX in place of Angular’s HTML templates.
- flowerlad 6y agoThis is a good example of how fucked up React can be when you religiously follow 'UI is a function of state' principle. A dialog should return a promise. Then you can mix confirmation dialog, fetch calls and so on like this: private async doTheThing(): Promise { try { await Confirm("Really do it?); // Promise rejects if user presses No const options = await OptionsDialog(); await makeFetchCall(options); await showSuccessDialog(); } catch (ex) { showError(ex); } } Yes, that's imperative code, which violates React principles. But it is insanely simple compared to typical React code.
- pietschy 6y agoReact examples like this always strike me as a great reason to avoid react, but then I'm an old OO dinosaur I suspect. In our framework (that has angular underneath) we go.. interactionContext.startActivity(MyConfirmDialog).then(result => ...); ...and the interactionContext handles back button clicks and makes sure the dialog is cancelled and our history is kept clean. It even works with nested interactions (i.e. the dialog can start a new interaction in place). Again, the user can click the back button repeatedly to back out of the interaction and everything just works. I've got no idea how you'd do that in React. edit - fixed typos
- mostlylurks 6y ago>I've got no idea how you'd do that in React. It's simple, you can place a component that provides dialogs near the root of your component tree and provide async functions which open such dialogs to the descendant components (e.g. via contexts). These functions can then be made to resolve only when the dialogs are closed. Thus, all descendant components can use dialogs in the same manner as shown in your example.
- Swizec 6y agoThat’s exactly the API I expose when implementing modals in React. You don’t have to make life complicated.
- mostlylurks 6y ago
- btown 6y agoIf you're building a tool used in B2B, data curation, or internal tooling uses, I highly recommend creating or finding an interface for confirmation dialogs whose invocation is as simple as `await confirm("...")`. You can even have a hook that lets you memoize and wrap a callback with this, perhaps even making it easy to condition on having unsaved changes. Have it mount in a place that makes sense in your own setup (you can use context, or it can just mount outside your main React mount if you want to cheat), with styling that matches your design system. You want to make the friction for colleagues to use this as low as possible, so that it will be used by default for dangerous actions, without cluttering your business logic! (Of course, in implementing such a thing, a state machine is a great choice, and OP's article covers much of the complexity you'll need, though possibly using a library vs. a reducer is overkill!)
- rvanmil 6y agoThank you, this is really great advice. These kinds of dialogs are always a hassle and are often skipped for that reason.
- joshxyz 6y agoI'm using sweetalert2[0] which is good for success error info messages; warning / question confirmation dialogs; and input / select dialogs. Also got a nice optional timer and the animations can be disabled (we do lol) [0] https://sweetalert2.github.io/ https://sweetalert2.github.io/
- btown 6y agoSelf-replying to add that it's important to know when not to use a confirmation dialog (for instance, in a "critical path" of rote data entry), and that your customers may organically show you that things are critical-path that you may not have thought about. In that case, though, having a good abstraction for confirmation dialogs is even more critical, because it's easy to remove (or A/B test) without feeling like you're throwing out significant amounts of effort; it becomes a zero-point story, so to speak. When you're writing less code, there's less code to remove when that becomes necessary, and that's why abstractions are such a big part of our roles!
- deleted 6y ago[deleted]
- ricardobeat 6y agoThe over-engineering in React land is really getting long in the tooth for me, and I think will be its downfall. This took under 10 minutes, including creating the modal, and I barely had to think: https://svelte.dev/repl/0299705b5e9e46be9e87fe4fef035bec?version=3.32.1 https://svelte.dev/repl/0299705b5e9e46be9e87fe4fef035bec?ver... No 'finicky useEffect', no race conditions, easily packaged as a component, no dependencies and maybe a couple KB in size. Easy to understand for everyone.
- purplecats 6y agoagreed. some workplaces use it as a competitive tool. build an overengineered product to make it look like you're contributing more value than your coworker etc.
- mssundaram 6y agoRight, but your example has nothing to do with a state machine. Nearly the same code could be written in React. I don't share the author's same view of "finicky useEffect" etc. Using a state machine for a modal is pretty overengineered, but I don't think that's reason to switch away from React altogether.
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- dceddia 6y agoOP here! Thanks for putting together the Svelte demo. I'm a big fan of Svelte too, and I concur... everything I've tried to build with it so far has felt simple and easy compared to React. FWIW this post wasn't written around the idea that modals in React are so hard they require a state machine to get them right, more that I wanted a minimal-ish example to show off how to build something with a state machine, and ideally something real-world-ish, but that wouldn't require special domain knowledge. It's always a tough balance to strike when choosing examples... too simple and they come off as overengineering, too complex and you lose the forest for the trees. Thanks for checking it out, at any rate!
- dceddia 6y agoAuthor here! I wrote this post last summer after getting a chance to use Robot on a project to manage some complex state, and wanted to write an "intro to state machines" in the context of React. We used it for things like scheduled periodic syncing of data to the server, managing app initialization when data could come from the server or localStorage and could fail (in an offline setting), and a couple other things. It gave us a lot of confidence in the code and I thought it'd be worthwhile to try to get the idea of state machines in front of more people, with a simple (and somewhat contrived for the sake of) example. From the other comments I gather folks are taking this as a recommendation to use a state machine for modal dialogs, or a suggestion that this is necessary in React. My intent was more to introduce the concept of state machines with a simple example, not so much to say this is the right way to build modal dialogs :D I think state machines can really help simplify some kinds of problems, and I've been on the lookout for places where they're a good fit. I'd be interested to hear examples where a state machine was useful in code you've worked with.
- darkteflon 6y ago> My intent was more to introduce the concept of state machines with a simple example ... Okay, so, I have a question about state machines but I’ve been too embarrassed to ask on HN. This is my opportunity, so I’m gonna jump right in. My question is: why don’t we see state machines _absolutely everywhere_? Or, do we, and I just haven’t appreciated it? They’ve been introduced in the article as a great new alternative to - presumably - whatever you’ve been doing to manage your (in this case, UI) state up until now. But aren’t state machines self-evidently the best way to manage any arbitrarily complex combination of states and transitions, regardless of the problem domain, language, etc? What are the sane alternatives? A number of commenters to this thread have said something along the lines of: if you’re not using state machines, you’re using state machines without knowing it. Perhaps that explains the blind spot? All this being the case, why do they seem to get so few explicit mentions, eg, here on HN or on SO or otherwise? Love to hear people’s views - please set me straight!
- scns 6y agoIn Elm/ReasonReact before Hooks it/was the only way to write your application making impossible states unrepresentable
- cccc4all 6y agoReact has built in hook to handle state in components, useState. It's the first hook that software engineers learn in React hooks.
- shadycuz 6y agoI believe the point of the state machine wasn't to save the state but to show how to handle transitions from one state to another using a state machine.
- nine_k 6y agoI think I make state machines with React all the time! Basically what you describe in Redux reducers is state machine transitions. I thought it's the entire point, and common knowledge?