Things I wish I knew about state management when I started writing React apps
medium.com
medium.com
> What makes these frontends complex? State management. The frontend “knows” a lot of things, and these things interact with each other in non-trivial ways [...] Example: dark mode support. For example, say your app has a dark mode. All your rendered components must know what theme is on, so they can render the UI in the right color.
Back in my day.... you'd slap a class on the <body> tag, and you'd have all you'd need for your CSS to style the rest of the page accordingly. How is using Redux an improvement here?
None of the examples of "complex" front-ends would be at all difficult in an old-school "just re-render the page" setup using something like pjax or turbolinks. And in my experience (seeing web pages with > 1MB js files) it'd be faster/more responsive for the end-user too.
React/Redux are a replacement for what people used to do with jquery or plain JS. They're not for display; they're for complex interaction of the kind that used to require a native user interface (Java Swing, Gtk, Win32, etc). Javascript is a mess, and the DOM is a mess; React makes that slightly less awful. (Redux is an extension to React to help manage global state; again, it's compensating for just how bad Javascript is.)
Bonus: React encourages the use of JSX, a language that makes a lot of behavior look as if you were writing it directly in HTML. You don't need React to use JSX, and you don't need JSX to use React, but they do dovetail nicely.
You can write in plain JS/jquery if you want. Plenty of people do. React is a framework to make that a bit easier; that's all.
Another observation that I've been finding interesting about "modern frontend development" is that despite all the arguments about how React makes DOM manipulation easier, people still prefer to defer to libraries to do any non-trivial amount of DOM manipulation rather than actually use pure React to do it. This is exactly the same modus operandi as when jQuery was "modern frontend development" ("it makes it easier to work w/ DOM", plus a ton of opaque jquery-* libs)
Could you give an example of a commonly-used library that serves this purpose?
Both of those examples are _specifically_ about using React to do the actual DOM manipulation.
React-Bootstrap is a set of React components that know how to generate the correct HTML structure and classnames to get the matching Bootstrap styles. As part of that, I believe some of the jQuery-centric logic in Bootstrap has been implemented in just React.
For drag and drop especially, you're having to approach the problem from a different mental model. You can't just `$(".my-list").sortable()`. React wants you to describe your UI based on _state_ rather than doing raw DOM manipulation, so libraries like react-dnd and react-beautiful-dnd do the work to tie together drag events with the underlying logic to drive React re-rendering.
But in both these cases, you're not "using other libraries to do DOM manipulation". React's doing the work. These libraries are just helping tell React what the DOM should be.
The thing is that we could s/react/jquery/ and end up with a more-or-less believable argument for why "jquery is needed for complex apps": You can't just `draggable=true`. jQuery wants you to describe your UI in terms of a plugin's options object rather than doing raw DOM manipulation; libraries like jquery-ui do the work to tie together drag events with underlying logic (e.g. snap) to drive jQuery re-rendering. You're not "using other libraries to do DOM manipulation"; jQuery's doing the work. Etc.
Of course a library on top of react/jquery/whatever will generally be written in a way that is idiomatic for code written on top of its respective base framework. For library writers like you and me, it might even make sense to argue that React rendering model is nicer for writing libraries on top of than jQuery. But that's a developer-oriented mindset, which, as the_gastropod had alluded to, it's very often at odds w/ a user-oriented mindset.
The "old beard" approach slices through all the abstraction layers and simply points out: huh, you don't need context and css-in-js and themes to do dark mode, plain CSS fits the bill. And when one resets assumptions enough times and start noticing more often than not that "complex" projects turn out to be glorified CRUD apps, one might indeed start to wonder if the abstractions are really as fitting for the task at hand as they should be.
If state management is so critical to running a react app than I feel Elm/ClojureScript is a better choice than react to build web apps. But I doubt they will be as popular given network effect.
Now everyone went on a bandwagon that react/angular/SPA/progressive apps is the only future and plain HTML/CSS/JS mix on server side with specific language templates is dead. I am sure we will again go full circle back as you can see now with SSR (server side rendering) is considered as a norm for performant react or angular apps. SSR combined with complexity of react/redux unnecessarily makes everything complicated (worse than even complex). Instead of doing HTML templates in nicer language on server side now people are forced with half baked solution on client side like JSX or angular templates etc.
A developer not just need to learn intricacies of HTML/CSS/JS, but then API's of framework like react, combine it with state management framework, combine it with client side templates, combine it with build tools like gulp, webpack, combine it with NPM, Yarn, combine it with babel compiler, combine it with many testing frameworks. The whole eco-system is very complicated (not just complex).
This is one of the reason for emergence of compiler based web application development language and frameworks like Svelte, Elm, ClojureScript, PureScripts. They will still be not popular because there is a network effect of jobs available. So inferior tools for user interface like react/angular will continue to thrive.
Well it is not dead, but with the rise of Webapps (not Websites!) come use-cases you can only handle with SPAs. One major example is the new youtube feature, where the video playback is not interrupted when you click on a link somewhere (the video stays at the right bottom corner of the page and keeps plaing). No way to do this with SSR.
Other examples include staying in fullscreen mode (think automatic playback of video playlists) - with SSR, every video change would trigger a page reload and thus require user interaction to go back to fullscreen.
SPAs are not required for Websites, but for Webapps there are a lot of cases where SSR is not possible or would result in poor UX.
A decent dark mode is going to take a lot than a class at the root in any case (a class at the root makes a good toggle, sure, but you have to do all the styling if particular elements that that toggle applies.)
If you have handrolled CSS for your light theme and want to add a dark theme toggle, hand rolling CSS for it and toggling with a class at the root is pretty idiomatic React.
If you've chosen a styling/theming library to solve other problems, or incidentally to doing so as might have happened if you chose Material-UI, then, sure, a class at the root might not work well, and/or you might have a much easier solution (not easier than the class at root but easier than the rest of the work of implementing a dark theme) for adding a toggleable dark theme. Like in Material-UI, where you just tell the theming solution you’d like the computed dark version of a theme.
Of course, all of the above functionality can (and has been) implemented in jQuery, or vanilla js. But using a framework makes it easier to structure and edit the project.
The server still needs to validate, no? I've never seen a form that really benefitted from client-side validation. As long as required fields are clearly marked, I don't really understand the value here.
> error messages
Hmm? What about them needs React?
> update of data pushed from server
You can poll very cheaply. And even Websockets are pretty simple. Check out Phoenix LiveView or Rails Action Cable for decent examples of trivial solutions to this.
> sharing markup/functionality between pages
Even Apache server-side includes let you do this. We're not talking about hand-rolling plain HTML files in MS notepad (or even going wholly js-free). We're talking about React being overkill in a lot of places.
Required fields are typically not the only type of validation required by a form. I think a designer would see the value of client-side validation more easily than a developer. It's not to ensure correctness - it's to give the user quick and actionable feedback.
> You can poll very cheaply. And even Websockets are pretty simple. Check out Phoenix LiveView or Rails Action Cable for decent examples of trivial solutions to this.
React isn't helping you poll - it's helping you structure your application in a way that ensures all components receive the updated data.
Any non trivial form is going to have non trivial validation. Validity for some fields will depend on other fields, the validity for a single field could have complex rules, and forms can be lengthy. It helps the user to get validity feedback without sending information back and forth from the server.
> Hmm? What about them needs React?
> You can poll very cheaply. And even Websockets are pretty simple. Check out Phoenix LiveView or Rails Action Cable for decent examples of trivial solutions to this.
Like I said in my OP, none of the things I listed require React, some dont even require JS. But having worked in large codebases of jQuery and large codebases of framework code, frameworks scale up better. LiveView is cool tech though.
> Even Apache server-side includes let you do this. We're not talking about hand-rolling plain HTML files in MS notepad (or even going wholly js-free). We're talking about React being overkill in a lot of places.
If you need to share markup with dynamic values, you need a full programming language. I'm aware that there are many solutions for sharing markup, but there are fewer solutions for sharing markup with dynamic behavior. Again, a framework scales up better.
To be clear, I'm not suggesting React for everything. But the GP comment was suggesting it was good for nothing, so I provided counter examples.
Client input should never be trusted. Server needs to validate, but that doesn't mean client shouldn't validate as well.
> I've never seen a form that really benefitted from client-side validation. As long as required fields are clearly marked, I don't really understand the value here.
Client side validation is not only about required fields, it's much more than that. Have you ever used a web store where you need to enter your shipping address, credit card info, etc? Address, zip code (or worse, UK style postal code), phone number, email, credit card number - all that needs to be validated.
Sure the server can (and should) validate that. The problem then is how to let the user know they've made a mistake and how to fix it. Accessibility issues aside, users need to be told that they've made a mistake as soon as it is made, otherwise there is a chance they won't find the error message, and simply give up trying. Lost sales is the reason client side validation was invented.
> Hmm? What about them needs React?
If you don't want to submit a form with full page reload, you need to make an Ajax call with form info. When response comes back with a list of errors, you need to walk through the fields on the page and mark the relevant ones as invalid, with corresponding error messages displayed _close_ to the input fields. Of course that is doable in plain JavaScript, and doing that seems trivial for one form. Then you have to duplicate that code for another form, or abstract it in a library... Congrats, you're on your way to reinventing React. Or worse yet, Angular.
> You can poll very cheaply. And even Websockets are pretty simple.
When the results of that poll come back, you need a way to display them. You need to decide which part goes where, which elements to hide and which to create, etc. The mechanics of this is what React (or a similar library) does for you, so that you could concentrate on writing logic instead.
> We're talking about React being overkill in a lot of places.
React might be overkill in a lot of places, but it's extremely hard to tell where and when. If you start with a simple hand rolled JavaScript app, at some point you might realize that maintaining it is a chore beyond one person's capacity, and hiring someone to do that for you is plainly impossible. You should have started with (React|Angular|Vue|whatever) in the first place, and now it's a choice between ground up rewrite in (React|Angular|Vue|whatever), or long stagnation and eventual death of your business. Do you want to take that chance?
Optimizing for developer productivity is the only safe bet in the majority of cases, unless we're talking about a personal project with no commercial value.
> They're forms that submit stuff to a server and get a response.
So it sounds like they're not actually doing any of the things you mention. For basic forms that just submit, of course React is overkill. Hell, a software developer is overkill for that. For any of the things you mention, React or another similar UI framework makes the project much more manageable.
We decided to split it into an MPA, adding React components to server-side rendered pages as needed (along with intercooler and stimulus when we don't need the power of React).
Things have become much simpler and quicker to develop, allowing us to focus on the user features instead. It's easy to forget how much you get for free from the browser and traditional HTTP/HTML client-server mechanics, along with the ease of having full access to your database and data model in your templates - and makes you wonder if going with an SPA first is throwing the baby out with the bathwater.
Minor correction, React came out about 7 months after Typescript. That said, Typescript had a promising birth but a rough infancy, so I wouldn't blame anyone for considering it released only after 1.x versions starting in late 2014.
In my experience if the application state is overly complex then that is a result of the design, and that usually leads to a bad experience for the user.
Ah but if you actually looked at what you built, I am 100% sure you folks built your own framework instead. A framework that has no community support, no stackoverflow articles to help out and most likely minimal explicit documentation. You will never be able to hire talent that already knows the tools for your framework. You'll be spending all of the next eternity cobbling together features that you could have had out of the box had you adopted somebody else's framework instead.
The choice is never "use a framework" vs. "don't use a framework". The choice is "do we use somebody else's framework" vs. "do we build our own framework". What you implicitly and unknowingly chose was the "we are gonna build our own framework" option.
In my experience, the minute the few folks who pushed the "no framework" option leave the company, all the rest of the developers smile and scramble to replace the hot mess of a codebase with something using an industry accepted framework.
before being involved in mostly "modern saas", i was heavy in electrical/electronics mfg and there was always a tension between "not-invented-here" and "borrow-buy-build" camps. NIH would stress that all components in our designs should be in-house (electrical metering, data acquisition, etc) while the BBB camp would turn every effort into a 3rd party integrations exercise. at times this even applied to the factory floor. i seriously know a person who was like "hrm, lets build our own pick-and-place for the smt line..."
I mean, how hard could building our own pick & place be, right? Once you strip out all the bloat the vendors add to make a sale, it is nothing you couldn't do with a couple servos and a $5 Arduino, right?
I think the "build everything ourselves" attitude comes from a not at all understanding opportunity costs and comparative advantage. Plus in the software industry, tons of developers I've encountered are super paranoid about "vendor lockin" without realizing that once they roll their own library/framework/pick-and-place-machine they have effectively locked their employer into a vendor as well. Only instead of a third party vendor with all the benefits that may come with it, they've "purchased" a product from a really shitty vendor--themselves!
It is never a choice between:
"Platform or no platform"
"Framework or no framework"
"Vendor lock-in or no vendor lock-in"
You will always be on a platform. You will always be using a framework and you'll always be locked into a vendor. The trick is to make sure you don't lock yourself into a shitty vendor who makes a shitty platform or framework.
My reasons are:
I do not need to learn a monster
If framework goes down/becomes unpopular etc. etc. I could care less
Libraries are easily abstracted and replaceable.
Yes I/team do end up implementing our own framework but this framework is very tiny comparatively to you common monstrous frameworks and any sane person can get a grip in a day or less and it only has enough to accomplish a project.Also it can be easily sliced/diced and needed accumulated bits and pieces can be used for next project.
But there are gains too. The custom framework is probably much smaller and less complex, easier to know 100% of. It can be stepped through in the debugger. It's much easier to change and add features that your company needs, there's no outside bureaucracy to get in the way.
I disagree you're inventing your own framework too, there is a distinction to be made between libraries and frameworks. Just because I'm not using a framework doesn't mean I'm reinventing it, I could be using something like knockoutjs instead, or even postbacks.
In my experience if the application state is overly complex then that is a result of the design, and that usually leads to a bad experience for the user.
Though it is possible to overengineer this, you're probably underestimating ho seemingly simple apps can justifiably have complex state.Say you make API calls. Your application state just grew to reflect
1. Pending request 2. Request succesful or failed 3. Request result or error 4. Finished request.
Redux apps track all of this explicitly. The alternative to this is not reducing statefulness. It's just ignoring it with all the brings.
They're forms that submit stuff to a server and get a response
What kind of response? Asuming no React,a. json
b. SSR-html?
If,
a. Now you've got to process that response, handle errors and finally render into html. You'll be either imperatively replacing DOM nodes, interpolating string templates or both. Probably re-binding event handlers after that.
b. You'll be merging your server-rendered html to your current view. Re-binding event handlers if you've got any interactive stuff (e.g your SSR came with a modal link) and god knows what else.
React is a million times better than this.
For layout I use a mix of both pure and imperative functions that either creates a new element for every state, or modifies existing elements - however all isolated in a component or widget.
I try to use standard HTML5 elements rather then creating my own. But they are often inside a component or widgets which takes care of events like keypress, mousedown etc.
There are no string templates! No server rendering, just API endpoints. There is no JQery, there is no framework - besides the functionality for handling server messages and passing data to event listeners and callbacks. Some components and widgets are generic and can be reused in other projects. But most are specific to the app itself.
The advantages is that it's simple, fast, and customizable. The disadvantage is that layout is a bit tedious using appendChild instead of XML/JSX.
I use CSS for styling. I like CSS very much probably because I used to do web apps before CSS existed. To change theme you just change the .css file. Components, widgets and elements are not aware of theme and style, that's all handled by CSS. Animations are handled by CSS, and different screen sizes are also handled by CSS. Sometimes you need to the change components/widgets though.
The only advantage I see with frameworks is that you get to write XML/JSX/HTML, which makes it more easy to move things around vs just using JS functions and appendChild. But when looking at React apps the components are broken down into individual files anyway, so it's difficult to get an overview.
Probably the biggest advantages of going vanilla JS eg. no JSX nor frameworks, is that debugging becomes easier, and you do not need a build pipeline, just refresh or hot-reload individual functions. No bundles or package managers needed.
The points made elsewhere in this thread still stand: a bespoke framework might be conceptually simpler and much easier for a solo developer, but for larger apps that require a team of developers using a popular framework would be much more productive overall, as everyone is or should be on the same page with how features are implemented. This often results in poor UX where specialized knowledge about the framework is needed to improve performance and scale, which you as the author of yours know by heart and find it much easier to achieve similar or better results.
I'd say neither approach is inherently bad. Use whatever delivers the best UX for the project at hand, but know that you'll be paying a price when that approach reaches its limits.
No one is going to sit through a full-page reload on every form validation.
Even intermediate solutions that merged (hacked) client-server state failed too. APIs are just better.
Turbolinks is a more contemporary library that does much the same.
It doesn't need to be complicated.
Which returns document.write(messagecontent) //if message document.write(script tag t=n+1)
If there are messages, server returns them all immediately, plus the repoll JS, and if there aren't any, it waits N seconds before returning just the repoll.
This could fit pretty well into an IRC single channel model, where you just stuff all the messages into a single place. For something more akin to MDI/TDI where you have different message displays depending on who you click on, it's trickier to just shove the messages in at the end --- although, if you were a terrible person, you could do something like use the chat id with a prefix as a class name, and use that to show/hide. You still need dom manipulation to add new chats.
I think you might find several of these pages entertaining,[2] especially if you're familiar with text-based games (especially MUDs and MOOs[3]) and a Dungeons and Dragons-esque setting. The items, combat (PvP and 1-player), and any other aspect of chance were transparently dice rolls and modifiers in-game.
Because this is Hacker News, don't miss out on the >20-year-old anti-hacking mechanism.[4][5] Keep in mind this is for a sequel or companion game that came (IIRC years) after the original.
With that stage set, picture a black background and some grey text with scattered red/green/blue highlights. The logo was probably the only image. The page would reload every few seconds, but you could manually reload and see new messages sooner. If your username was in a chat text it would make the text larger (both the line and your name, I think).[6] Maybe under a few other conditions too: if you were fighting someone, if it was the admin/mod/dev(!) Falados, I think you could add a few of your own word filters at some point. Originally it was just your username in the line, I'm pretty sure. The combat moves and rolls were also part of this chat stream. I remember being in busy rooms with dozens of people, frantically refreshing, trying to read the top of the screen quickly before it was lost.
It was great!
I hadn't quite connected the games to programming yet, but I imagine the Hall or Arena you "joined" for Player vs Player combat and chat was by visiting the page of HTML generated by Perl scripts (I remember URIs ending in .pl, maybe some CGI thing?) that had a meta tag to trigger the refresh. It had a regular form with a submit button and the page reload was convenient because that was the only way to see your message or anyone else's new in the page.
I'm amazed how clearly I remember playing, if the details are a bit fuzzy. Looking for pictures I came across this,[7] and am excited to read through and maybe check out a beta. Thanks for prompting the search!
[0]: http://www.angelfire.com/games2/LanderZ/vq.html
[1]: https://web.archive.org/web/20000818004717/http://www.netdra...
[2]: https://web.archive.org/web/19990902013706/http://www.netdra...
[3]: https://en.wikipedia.org/wiki/MOO
[4]: https://web.archive.org/web/19991105152436/http://www.netdra...
[5]: https://crypto.stackexchange.com/questions/47177/would-sha1-...
[6]: FONT tags, I'm 99% sure. This was probably one of my early view-source learn-by-example sources, in a futile attempt to cheat
React is overkill for something that simple. You don’t even need JavaScript for simple forms.
Also, you stated benefits of Fact without stating how it makes it better.
For my job, with hundreds of developers working on a giant SPA blob with constant feature creep, frameworks like React are a necessary evil to maintain the insanity.
If you have a small team of 3-5 people who understands the architecture as a whole and can keep up with most PRs then I don't think a UI framework is necessary.
However, if you have many teams working on the same project and/or cannot keep up with every single PR merged then I think using a UI framework is a must. It forces you to stick to strict rules. Although you will start writing boilerplate code, it makes the entire application much more modular, and easier to reason about.
For instance, I’ve seen a growing trend of people creating a whole DOM using React JSX so that they can use React’s server side rendering pipeline. <body> tag all the way down. But the actual reactive components make up about 30% of the page. So you’re wasting download and JS processing time (a serious concern on low end devices) hydrating a lot of elements that won’t even ever change. It’s stupid and it needs to stop.
But for fully interactive webapps, React is great.
You hydrate the markup because you want to render subsequent state changes in the client. Meaning much smaller downloads (just the relevant data) between route changes and so on.
I suggest you open the network inspector and see what happens when you hover over links, navigate and so on. The experience is very fast and responsive on initial page loads and even faster on subsequent navigation:
The initial page load is as fast despite of hydration because many of the requests are non-blocking and/or happen after the raw page content is loaded, including JS scripts.
Further navigation triggered loads just load small amounts of JSON while many of the assets and common UI elements are already there. In this case the data is even loaded while you hover over a navigation link.
Yes, this assumes that you actually navigate through the site and yes it does more than you sometimes need if you just view a single page.
But the trade-off seems to be very good here from my perspective, especially because one cannot know if the visitor will navigate on the site past the initial page load or not. The content on this site is also structured into small subject-pages (see: the docs sections), which synergizes well with this frontend pattern.
On top of that comes ease of development, tooling and automation around the react ecosystem (you'll find similar in other frameworks). And the mere fact that there is much less coupling between the rendering/gui side and the data/server side, even though most of the site is rendered on a server, because the frontend code lives in one cohesive framework.
A lot of React sites I see in the wild have none of this. They have a multi-MB JS blob that gets downloaded immediately on load and parses a giant JSON blob stored in the HTML that represents the page state.
I'm not saying React isn't useful or that it can't be done well. I'm saying that in a lot of cases it just isn't done well. Whether that's down to a lack of education or a lack of care I don't know.
The components work with data. Let's say you need an auto-complete component - 500ms after the user starts typing it will query the server and get some data back. You can describe the returned data in partially rendered HTML too but the attribute-based descriptions are often quite limited and for ease of use from the JS side you're likely to end up with embedded JSON as a general data description language with unlimited nesting, which naturally leads you to a client-side templating language.
The components are also likely to re-use each other, giving rise to the need for a module system to track those dependencies and make sure they are all loaded when you go to a specific page based on the components being rendered. If you use partial rendering you would also need to ensure to track any components used from the partial html endpoints have their JS included in the page, capturing any of their occurences, making sure to attach the right event handlers to enable interactivity.
Sure, one could ostensibly write a set of components that use JS under the hood and add them to the back-end. They could also write a server-side module system that ensures that the right scripts are included in any page that uses them. They could do all that.
But then that would work only for a single backend ecosystem, such as e.g. Rails. There is no way this component library would be able to compete with a unified client-side ecosystem in terms of features, flexibility or battle-testedness. Additionally, if you want to try a different language on the backend, you won't be able to - unless you're willing to pay the overhead of a secondary service meant to only generate the HTML and manage the JS plugin system.
And all this is before we even consider alternative clients (e.g. mobile)
Typically what people say is "oh most websites won't need THAT" but the appetite for interactivity only grows with most companies, so you could easily end up in a slowly boiled frog scenario where your serverside HTML-JS mishmash is an absolute mess.
The modern way to do dark mode is to assume that people who want it won't be using IE or Edge, and just do a CSS media query. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...
1. Componentization. I've found this to be super useful and helps me build items that I can use in multiple places. Pair that with something like Storybook JS - and you have an app that can be broken up into puzzle pieces with high reusability. This helps isolate the business logic from the UI. Granted - a similar effect could be pulled of with HTML templating.
2. State Management - Love love that I don't have to handle UI updates when the data changes. This I believe is the whole "reactive" model [if I'm not mistaken]. Consider building a Rich Text Editor, and the user is typing, he uses keyboard shortcuts to enable bold, underline and italics. How do you update the UI to reflect those have been selected. Listen to the shortcuts, and add the necessary class? Great, what if that shortcut had to kickoff ten more things? Another method also adds a contradicting class to that same element. At some point, the logic becomes super entangled, and it becomes hard to track where and why. With React/Redux - life runs like an FSM. Each update/action can be traced.
[though I use MobX because it takes less effort]
P.S. I also don't recommend React for Single Page things. It's unnecessary bloat. Complex in my head means multiple distinct components (like > 5).
- Combining multiple components in a given page to transform or process user interaction becomes simpler as the components are plug and play with the state of the page.
- Combining multiple pages where state can move across an entire application can allow for a seamless user interface
- Employing techniques like code splitting and Server Side Rendering allows for smaller js payloads with an incremental approach to assets versus loading everything at once.
The largest advantage UI frameworks provide is that it makes what you would normally do with javascript a lot more _composable_. You can do the same with vanilla JS, but it gets a lot more difficult when you attach events to the DOM, it's hard to reason about how you might organize your code which doesn't scale well across teams.
I think the simplest way to consider the advantages of a UI framework is to build a search application from scratch with vanilla JS. You can get pretty far but once you start adding in things like pagination, it can get a little hairy. jQuery and it's libraries can help you there, but there's a good chance that you're adding in some overhead to the load times of your page.
If you rebuild the application with a UI framework and employ practices like code splitting, the code (usually) ends up being a lot more reasonable and easy to extend (which businesses tend to like). Adding features becomes less about reasoning about which DOM event fires what and instead more about transformations in state with the components subscribing to state instead of DOM events.
I agree Redux isn't an improvement there, media queries for user preference[0] were. However, rendering details aside (this may still use a CSS class), you might choose to give a separate option inside your regular UI or have some other interactive blob that is colorscheme-aware but needs to be told that manually. Maybe you have lots of options. The app-colors state can be set to some explicit value initially or default-inherit from the environment, but it still has to be tracked somewhere that isn't the stylesheet. It doesn't have to be Redux but it does have to be something.
Relatively soon I expect web components will start to eat into the UI framework nonsense.[1] React never really hid the need to know HTML, CSS, and JS well just like jQuery didn't. They made it _easier_. They also encouraged a plug'n'play ecosystem that made it easy to kinda-sorta hack together what you wanted -- without having to do much reading. Bold prediction: In the future we'll look back at a lot of "React developers" like we do at "jQuery developers" now.
[0]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...
[1]: https://caniuse.com/#search=web%20components shows Safari as the only partial out of major browsers, plus some in the long tail. I've only poked at it a bit recently, they were a mess last time I tried (years ago)... it's much better.
... but the purpose of state is to keep track of whether you're in light mode or dark mode. You don't want the arbiter of that knowledge to live on the DOM if you're doing a web app, because you need to reason about who is allowed to change between light and dark mode and the DOM is global state; anything in your app could be editing it. State (among its other uses) serves as a gatekeeper on the ground truth of what mode you're in (and if all changes to presence / absence of the class in question pass through that component state, you're good to go).
At the moment this is my list when coding frontend on my own projects:
1. The less js the better. 2. If you can solve a problem in a few lines of native js, go for it! 3. Some complex "component"? Use React, it will save you a headache.
Why like this? 1. A lot can be done without js. Solutions are many times simpler and easier to modify. 2. A lot of components, like models and so on can be done in a few lines of javascript. 3. I think React isn't bad per se, many times it's a cleaner solution over native js when it comes to complex components, which can be required by designers, shrug.
But absolutely, my speed of development when using React is lower than when I'm using server-generated HTML in a Rails app.
On the other hand, I can do things with React I can't do with server-generated HTML.
On a serious note, I do agree with you. oftentimes things are overcomplicated just for the sake of 'javascript'. Once you get used to setting up a project a certain way you may not stop to consider whether or not it's the best for the specific project.
When you feel like this, just remember two words, "developer productivity". That will be the excuse (reason?) for using it.
The one redeeming part of that section of this article is that it links to another article that isn't as wrong. The linked article details when prop drilling is and isn't a problem. And most of the problems it describes can be reduced by using TypeScript. https://kentcdodds.com/blog/prop-drilling
I couldn't make sense of the second section either. It sounds like it's recommending a global store as the solution to updating a single component. The correct state management for a single component is what comes with React.
A whole bunch of problems that were "solved" (or at least, had fairly good solutions) in the context of desktop GUI toolkits have resurfaced in the context of application development confined to the web browser.
Almost none of the previous "solutions" are usable, because of the specifics of the theoretically portable "browser platform" for which all this development is taking place.
So instead, an entirely new set of frameworks are slowly being developed to solve the same set of problems that desktop developers worked on 20+ years ago, and are slowly converging on similar approaches?
Is that too cynical, or just about right?
I don't believe the concept of one way data flow was in the old desktop app approach was it?
Nor was the idea of "props" i.e. a consistent mechanism for pushing parameters down to subcomponents through a consistent and flexible interface.
Nor was the idea of the render function - giving components a consistent function used to display itself.
Also gigantic leaps are being made in the area of simple componentisation - we are now down to very pure functions that handle a very constrained task.
State management in ReactJS especially with hooks is giving much more simple and clean mechanisms for handling cross cutting.
Old desktop applications, because they did not have a core application "cycle/flow" and were not arranged as a tree of components, had a tendency to end up as a spaghetti mess of complex highly coupled interactions with the whole thing risking collapsing under its own weight of complex interactions. Modern frameworks like ReactJS address these things by reducing complexity, increasing modularity via components, giving applications a general structure which is a hierarchy of components, giving all applications a consistent model for data flow, forcing a simpler mechanism for data sharing via the one way data flow rather than the more highly coupled two way data binding, and lately, emphasising the power and simplicity of plain functions over classes, objects and inheritance.
Surely there's other innovations too I haven't thought of.
Characterising modern web development frameworks as somehow only now catching up to 20 year old desktop development does not seem correct to me. Such an idea does perpetuate the myth though that the JavaScript world is full of dumbos badly trying to reproduce what all the smart "real developers" did a long time ago.
Web apps seem much closer to the database read/edit interfaces from that time (though much, much prettier and generally more sophisticated): there's a simple model (which does include one way data flow) - there's a data store somewhere, and the task at hand is some combination of:
1. present that to the user.
2. allow the user to enter new data.
3. allow the user to modify the data.
Oracle and others before and after them had a rich set of tools to develop primarily terminal-based applications for these purposes, which began to get "graphical" sometime around the end of the 1980s. These days, I suspect almost nobody uses that toolchain, and instead does the exact same tasks (again, much prettier, more visuals, more interactivity) via the browser.
Desktop applications started moving away from this model as "creation" apps started to emerge, probably starting with "desktop publishing" and moving on to include graphics in general, audio and video. By the time you get to Photoshop, for example, considering the task to be moving data either from or between a data store and a user interface doesn't really get you very far (though it's still an important aspect).
It is not true that old desktop applications did not have a tree of components. That's a core aspect of every desktop GUI toolkit that i'm familiar with. One thing that the desktop GUI toolkits didn't do very well was that tended not to support the original conception of MVC (not the current one found on the web). For example, when you interacted with some widget, it would change its visual state before anything took place in the "model". My vague impression of quite a few of the web component models is that they at least avoid this some of the time - the user interacts with them, and they (mostly) only change their appearance to display a change in the model.
I am not sure where did you get this idea. Delphi had it like almost 30 years ago.
1) The bloat cycle
2) The disruption cycle
The bloat cycle is where you have a “platform” that does more and more until it has a lot of features that most users don’t need. Eventually the cost of supporting those random features gets too high and there is pressure on people to hack together an alternative micro-platform that does some arbitrary subset of things. If that subset is actually a superset of “must-have features” for a large number of users the micro-platform becomes a new platform and the cycle repeats.
The disruption cycle is when there is a singular feature that is prohibitive to add to the framework, because of past decisions. Businesses working on the platform will just work around the hole, or fill the hole in a hacky way. This creates pressure for someone to create a new platform that solves that one unsolved problem. It can leave many solved problems unsolved and still ascend to dominance under conditions famously described in “The Innovator’s Dilemma”.
This would be something like “multiple users work on the same surface” or “works the same-ish on literally all the OS’es” features of the web.
These cycles might seem pointless, but they’re not because you end up in a different place than where you started. You get some fundamental simplifications and new features each time you go around.
Ideally as a user you want to time your entry into the cycle so you don’t have to deal with the regression phase.
- corporations
- governments
- TV shows
- music
- all human relationships
Sometimes you want to be with the group, other times you think it’s easier to be without the group. It’s part of being human.
I think these cycles are just results of human social behavior around software platform technology; whether they have a point or are pointless -- they can be either. Sometimes it's two steps back one step forward, other times the reverse.
When I google "cross-platform desktop frameworks", the entire first page of results are all about the modern toolkits like Electron, Photon, ReactNativeEverywhere, etc.
So it seems like your complaint is many years too late at this point. Maybe there were solutions to this 20 years ago, but if I'm starting from scratch today trying to build a cross-platform app, what I'm looking for are frameworks that are mature, well documented with lots of tutorials on how to do basic things, and have a large community around them for getting help on e.g. stackoverflow. It doesn't really matter what has been around longer, if the newcomers have surpassed it on these fronts.
I don't know whether there are more Electron apps these days around than Qt apps, but it's certainly neither outdated nor obscure.
But going somewhat deeper (and based on reading all the defenses of "React, etc" in the comments thus far), the fundamental difference is that if you were using a desktop GUI toolkit, you would almost certainly assume that there's essentially zero cost (or almost neglible cost) to the communication between the data (model) and the GUI. This is (one of the things) that is so different for web development, where everybody is struggling to optimize away roundtrips back to the data (server), because it actually costs.
Put differently, whatever protocols might be implemented inside a desktop GUI to talk to a not-in-memory data source, the protocols are almost certainly designed for precisely that purpose. Whether its a vendor-supplied connection to an SQL database, an application-specific serialization/deserialization protocol, some IP-based message passing protocol ... whatever, it will all have been designed for the express purpose that it is being used for (more or less).
HTTP? Ahem. Granted, web app development has augmented HTTP in various ways (most dramatically would probably be websockets/webrtc). But fundamentally, the state management issue in a web app originates with the fundamentally stateless communication protocol being used between the app and the data sources/servers it is dependent on.
- The classic desktop GUI frameworks (Qt, wxWidgets, MFC, Swing, WinForms, etc) are based on a completely OOP inheritance and ownership model. You are responsible for instantiating widget instances, configuring them via setters, appending them to parents, and likely cleaning them up as well.
- It's been long enough since I've done desktop GUI dev that I can't speak to the state management aspect much, but I can certainly say that at the time, my own programming understanding was limited enough that I would have said my "state" was "whatever items are currently inserted in that ListBox". (I knew that forms of data binding existed, but didn't grok them yet.) I do remember Swing's JTable widgets have a distinct MVC setup, at least.
- Web dev is _heavily_ concerned with the number of bytes being delivered over the wire, so it's not just the number of round trips, there's the obsession with trying to find the tiniest library that can do what you need (combined with the nonexistent JS standard lib). With the desktop? Who cares, let's link in 50MB of Qt DLLs! (yes, yes, insert Electron joke here.)
Which is true, because model is what you’re working with and binding to (along with a controller). You can populate your models from a roundtrip data source, if it is not local. How is that different from web apps?
I think this is the good old “ancients were less smart” stereotype. Client-server and 3-tier apps didn’t begin with web. Heck, even lan latency/throughput/cpuclock were comparable to what modern internet timings impose.
It's really not true for any desktops in the "creation" realms that I mentioned. The data model for an image manipulation program or a spreadsheet or a DAW or a document preparation system is fundamentally in memory, and there's no roundtrip to anywhere to access it or to modify it.
while (!should_quit()) {
wait_for_user_input_or_other_events();
process_events(); // may mark some components for redraw
redraw_anything_that_needs_it();
}
So if user input or some data source indicates that some on-screen component now needs to be red with a yellow border, the "process events" stage makes a note of that, and effectively queues a re-rendering. The re-rendering takes place in the "redraw anything that needs it", at which time the code responsible for rendering can check current state and draw it red with a yellow border).Quite a lot of what is "cool" about frameworks like React is the extent to which they are essentially trying to rebuild this fundamental event loop logic in a web app context.
Gtk::Main app;
...
app.run ();No, it’s because developers are shamed and generally unemployable for writing code that doesn’t make use of a colossal super framework.
This makes me wonder “how did we allow this to happen”? The beginning of this article alone reflects the whole tragedy of modern programming. When I was a child we would just use common controls and the operating system would paint them whatever way configured in the control panel. Now we have to solve a complex state management problem just to implement a dark mode. Application programming was supposed to become simpler and easier, not this...
I don't get all the hate towards SPAs and JavaScript Frameworks here on HN. Everyone is basically complaining about complexity for applications, which are more than just a server-rendered form or message board (which is a way less complex applications compared to outlook or slack, where "realtime notification" is a requirement).
Application programming became easier by a lot, just the application requirements grew at the same speed, resulting in that feeling of stagnation.
With something like zustand[1] or the react context api, its just ~10 lines of code to store the dark-mode boolean somewhere central in the app and connect it to all the components without prop drilling.
Obviously there is the advantage of the web platform to leverage with JS frameworks, but there was something really nice about being able to drag and drop standard controls that all users were familiar with, and then just attach some code to them that talked to a database or whatever. I think that was the Parent's point that modern web development, while having the advantages of the web, is still more complicated than what was popular a decade or two ago for making UIs.
I asked something like this too, but the other way around.
"If Flash, Delphi, etc. were so good, where are they now?"
The answers were: These tools were good for one developer or small teams, but textual dev tools scaled better to big teams.
I mean, while these graphical tools certainly help, you have to learn them. I learned things like Photoshop, Sketch, Ableton Live, Final Cut Pro, Eclipse, etc. pp. and I can tell that it's a huge time investment to get up and running.
Maybe coding is that much easier, that people invest their time into this instead of a UI builder?
Maybe people got burned by Flash and now want to invest into somthing that lasts longer?
"The answers were: These tools were good for one developer or small teams, but textual dev tools scaled better to big teams."
Utter nonsense. They scale just fine. Whoever gave the answer seem to not know what he/she is talking about.
But we did more than just a server-rendered forms in delphi, vb and others! The hate is not for js or html (well, in this case at least). The hate is for invention of techniques which are hard to explain, follow, justify, implement and reason about.
Web had an enormous amount of seasoned and veteran developers around the world and threw this resource out the window by going a new fancy way of history repeating, which presumably no one did before and no one knew names for new methods when they appeared.
React/Redux is ST by the way, implemented in a language completely unready for that concept, but it’s unclear if its authors were aware of this at the time it was born.
I would find it strange, and probably switch to a competitor, if my preferred airline for example made me install (and keep updated) a desktop app in order to book a flight.
You know one thing I expect my browser to do that it has a hard time doing these days? Efficiently and correctly displaying text documents.
It has a hard time doing this because so many web pages think they need to be web apps and overthink things instead of leveraging the tools at their disposal in the browser.
Yes, if you're building a new Outlook or Google Maps in the browser it ought to be a web app.
If you're building the remaining 90% of use cases it should probably just be a web page with boring old tech. Add JS for spice if you'd like.
It takes too much Javascript, which is not efficient to parse or run, and is usually wasted on "looking/feeling fancy", not actual functionality.
macOS/iOS is a good modern example of the old way of doing things where you're stuck using this weird OOP-for-OOP's-sake Core Data abstraction that certainly doesn't feel like a local optimum. It's the polar opposite of simplicity. When things don't work, it gets complicated very fast as you try to credentialize in it.
By the way, client development was never easy. It's only gotten simpler over time. Painting a component to the screen (your example) whether you can use a built-in or have to make your own is still the easiest part of client development, not the hard part. State management and deciding on when to repaint is still the hard part. And just because the runtime gives you a solution doesn't mean it's simple or ideal. So I'm wondering which specific client development experience you're looking back on with such rose-tinted glasses.
What they're saying is that if you have a large complex system, it would be easier to implement dark mode with good state management. Not that you need complex state management to implement dark mode.
Instead, we've managed to turn the web into a full-blown application platform. Do you think Tim Berners-Lee envisioned something like Google Maps being created in the browser?
Now, I concede that JavaScript isn't the best language for creating client-side apps. I hope someone applies some first-principle thinking and creates a language expressly designed for this purpose.
Considering how many people and developers use web apps, this could be an area where someone can have a massive impact.
I wonder if WebAssembly is the solution here?
At the very least, using something like formik shouldn't be a necessity. There should be some qualifier that it's only really needed for very complex forms. Or am I missing something?
Doing it by hand involves a ton of repetitive work.
Each form input needs to have its input validated client side (immediate errors), needs to have error messages from server side validation, needs to be accessible, needs to work across all browsers, any sort of drop downs from an input need to have proper a z-index, and state, so much state to track.
Doing it in vanilla Redux just sucks, and it is super easy to completely kill performance. Behold the number is sites that have a non-trivial delay when typing individual characters into a form.
(I've seen input delays on sites approach 500ms per character!)
(Of course 90% of this goes away by letting the browser handle forms, and accepting page refresh on submit, but if you want to go SPA...)
One of my larger regrets from my last project is not just learning a form library, it would've saved me literally weeks of work.
Although that entire site is a worst case, it is 3 giant forms each with over a dozens inputs varying from plain text to numeric to date-time to an image uploader and gallery manager.
Basically the best case scenario for "I should've learned a 3rd party library".
I guess my larger point that might not have been emphasized well enough is that recommending using a form library for _any_ form seems misguided.
It just blew my mind how you can write complex forms with validations in just a few lines of code.
Give it a try.
* The concept of pristine/untouched inputs
* Basic and custom validations
* Resetting forms to various states
* Prepopulating data based on an API response
* Dynamically creating inputs based on UI states.
* Normalizing data upon input or submit
* Creating wizard/multi-step forms
All those implemented in Vanilla JS or in a React component can get messy. Is redux-form or any form library absolutely necessary, nope, but they sure do help.
I tried patching the form lib to update it from React 0.14.1 or whatever we were on way back then, to the new React 16 hotness. I eventually gave up because the code was so generic that I had to patch use-cases I'd never even thought of (good old feature creep).
Meanwhile, our home-grown forms using plain-JS, hooking into graphql, pretty much did the job with only simple abstractions. The same was to be said for the SVG-based clip-path polyfill I wrote to support IE. I had no problem writing a basic, self-contained component for that.
Most of those form use-cases are quite trivial implementations IMO and the complexity comes from working around the dependency's limited API rather than the browser itself.
Blows my mind why some people choose to adopt megabytes of dependencies when you can just write a few HTML tags and have it executed by the browser's C++ engine instead of some JavaScript nightmare.
The platform provides enough to do forms. It's one of the few things the web is good at. It has been around for ages and works. I mean, what I'm using to type this is a plain HTML form. It doesn't need a massive JavaScript framework that heats up my CPU on every keystroke.
I felt this way too, until I used Ant Design's form component (which uses rc-form under the hood).
Form libraries take care of validation for you, allow you to specify an initialValues prop, and give you a handleFinish={values => ...} prop so it's easy to run your own API call when the user clicks "Submit".
Plus, Formik lets you model the form values using a TypeScript interface which I like.
To me, Formik provides several advantages over not using a form library, and no disadvantages except a small learning curve, so that's why I recommend it.
- Easy validations (either with a single schema object or field-level)
- Easy / reusable form controls
- A context containing the current form values, errors, what's been touched by the user, and methods to manipulate the form / its data
[1] https://github.com/react-hook-form/react-hook-form [2] https://github.com/jaredpalmer/formik
Jesus don't do this. Mobx, Vuex, Redux, etc, are about managing application state not about managing application logic.
I started making SPAs in 2015 and I also used actions for everything (API calls, auth, etc). It was fine for small projects but then I got to work on a medium sized project and those actions became huge and controlled everything in my app. I usually consider myself a pragmatic person but that was impractical for a number of reasons and also conceptually wrong.
When working in SPAs now I remove anything that is not state management from MobX/Vuex/etc (I don't use Redux anymore). That logic belongs somewhere else.
The pattern I'm doing now is that I conceptually separate components from the application logic. The store(s) feed data to the components but if the components want to interact with the application they go through a mediator of sorts (eg: changing a route, modifying state, authenticating, etc). This mediator then interacts and coordinates with the different application modules.
For example when authenticating many things have to happen in different modules (API, store, router):
1) An API call has to be made to verify credentials and receive a token
2) This token has be stored somewhere eg: localStorage
3) Maybe we also need to make an API call to get user info, their roles, etc
4) Some state in the store has to be modified. Not only the user info, but the app logic usually needs to know if a user is authenticated.
5) We need to tell the router to go to somewhere eg: /home
It makes no sense to do all that in an action. Store actions should be about managing state, period.
Although I do not agree actions should be responsible for triggering application logic. It is a very popular pattern indeed, and I've used myself a number of times, but my code makes a lot more sense now that store(s) are only responsible for state management.
Personally, I don't see that as a huge problem. Better in an action than sprinkled into components. As someone else mentioned, redux-thunk makes this even easier (and one could argue, promotes this).
Logic flow doesn't have to be hard to do.
> Better in an action than sprinkled into components
Totally.
Example: okay I have a bunch of tabs that show different views. The state for which tab is selected can live with the tab container. Months later I find that I need other parts of the app to be able to switch to a different tab when user actions happen.
At this stage all I put in local state are the most basic things like dropdown open/closed state.
I think of it like a dedicated state 'portal' - should I need to use one particular piece of local state in other components, I just write a quick dedicated Context for that one piece of data (super quick and simple and React-y using the new API) and simply set the context as well whenever I set that piece of state.
Won't work for everything, is a little hacky in a way and you wouldn't do it that way from scratch, but it's such a quick and easy solution to this issue compared to a major redesign of the global/local state.
And I wrap almost everything in hooks anyway, so the component doesn't care if it's local, Context or Redux: it's just data and I'm free to move it in different stores.
Tabs are typically the sort of thing that makes me think "context" immediately. Also implemented nested collapsable sections with this, TikTok-like feed where different components control the same UI component (sliding panel), tables...
I never thought of the existence of the global db as the thing that ALL state should go in., or is that a community best practice?
Edit: after digging through the re-frame docs themselves, you can see here that local reagent atoms are used for local state even with the global app db: https://github.com/day8/re-frame/blob/69a3fcd411369fff65404e...
You can try this, but I don't think it'll turn out well!
You don't want to have a bloated, disorganized global store...what you're doing now, moving state into global state only if needed, is the right way to go.
https://redux.js.org/style-guide/style-guide#use-plain-javas...
Instead, we strongly recommend using Immer for immutable update logic, preferably as part of our new Redux Toolkit package:
https://redux.js.org/style-guide/style-guide#use-immer-for-w...
https://redux.js.org/style-guide/style-guide#use-redux-toolk...
In short, most of the benefits people _think_ they get from Immutable.js are just properties of immutable updates in general, and there's actually some pitfalls of using it. The only real unique benefit is faster copies of _very_ large objects.
I'm not saying it's not helpful, just that it's not a silver bullet in and of itself the way some folks think it is, and that you can get similar benefits without the overhead of its API.
We also created associated custom hooks that allow developers to easily "inject" a service into their React component (we use React's Context API for our "DI container").
From there developers can read the service state to drive the look of the UI, or send events to it to trigger behaviour that is encapsulated in the service itself.
Our most used service is a UserSession service that manages OAuth based login flow and session management.
The best part of this approach is that our service layer started in an Angular based proof-of-concept before we lifted-and-shifted it all into React.
How comfortable would you be allowing an outside dev to hack on your codebase without a _long_ conversation beforehand explaining the idiosyncrasies?
I guess it depends on the vintage of the developer. I would argue that Redux is an idiosyncratic technique for state management, and by breaking it apart into separate services, you get better separation of concerns.
I should point out though, that our “service layer” follows the same event-driven flow that you would see with Redux, but by breaking it up into state machines we can be more explicit about the states of our application.
“Service layer” doesn’t have to be a dirty word. Redux is a stateful, reactive service, and so are the xState machines we are using. Logically speaking, I don’t see much of a difference.
As for the _long_ conversation you mention, in my experience, it’s actually been a pretty short conversation followed by gratitude at not having to use Redux. I really don’t meant that as a slight towards Redux, which is a very elegant solution, but seriously, the people I’ve introduced to xState just like it better.
This allows a similar approach to redux’s actions and reducers (or events and `assign` for xState), but each machine can focus on its area of responsibility and enforce its behaviour through defined state transitions.
This approach also makes it easy to coordinate between multiple services (e.g. having the “NavigationService” change which links should be available by listening to a state change on the “UserSession” service as a user becomes authenticated.
This started as a bit of an experiment to see what a stateful UI “service layer” might look like, but as we’ve progressed, we haven’t found a need for anything else like redux or mobx.
Could you expand on how you're achieving this? I'm creating a new project using xState at work and this is one of the things I've been trying to come up with a solution too.
My biggest problem is the pattern I've set up for routing on top of xState (It's an electron app so no URL management required which makes that easy) requires you to create the state machine yourself, so it can't for example be used as an actor in part of a larger system.
It sounds like your NavigationService and UserSession service might be similarly separated so I'm curious how they're communicating?
Something that occurred to me last night is that I can probably make a coordinator of some sort somewhere in the tree which forwards actions from state machine to another as required.
On start of the application we create singletons of our state machine backed services, and manage the dependencies between them manually. That is, we create an instance of the UserSessionService and then pass it into a "constructor" for the NavService. That constructing function creates the NavService and then wires them together by subscribing the NavService to listen to the state transitions of the UserSessionService.
I suppose if the code base grows and you have dozens of services to keep track of, it will get pretty unwieldy, but that is no different than using a fancy IOC container in Java/.Net. We plan on keeping things simple, while still braking apart the single store you'd have with Redux.
I hacked together a simple gist to show the basics of how we wire things together (without getting into the specifics of how the internals of the state machines work). [1]
[1] https://gist.github.com/adamkl/656008691d42220eddf8bfe147753...
I'll have a think about this and see if I can work it into my structure as I'm certainly going to need inter-machine communication as it grows.
The `makeMachineRoutable` function in this gist is what I'm using to consume my state machines in React. https://gist.github.com/UberMouse/49ff91e95390265d63d3ac1bc7...
It also shows my prototype for xstate based declarative data fetching, though I have sinced switched to using observables instead of promises for representing my GraphQL query results so I've changed it quite a bit.
Trying to write React applications in an imperative manner (the more traditional approach to programming as opposed to declarative), twisted my head in knots and made things harder and more complex than they needed to be.
What I mean by writing React applications in a declarative manner is:
1: you have a global state in your application
2: the global state includes an object with information about what state the application is in, for example:
globalState = {
"DisplayingFormEditUserProfile": false,
"DisplayingFormViewUserProfile": true,
"DisplayingSideMenu": false,
"DisplayingTopBanner": true,
"DisplayingUserSignedInStatus": true,
etc etc
}
3: use React context to manage this state.4: components decide to display themselves by querying the global state
It becomes easy then for your buttons and links to simply adjust the global state to make things happen. Because React rerenders the application each time the global state changes, you need do nothing explicit (imperative) to drive the application - it all just happens automatically as a result of what actions the user takes - actions that alter the global state.
React hooks and context make this approach very easy and clean to implement/
That's the way I do it anyway - an approach I find makes the entire application easier to understand.
Our team uses hooks and context for everything, and a little bit of legacy redux usage.
You don't need to put the username of the logged in user into Redux, but if multiple components across different pages need to access this data, you should make it globally accessible.
You can choose Redux/MobX for this, or you can roll your own solution with context and hooks.
I'm actually learning more about the context/hooks approach myself.
Kent C Dodd wrote a great article on this topic I'd recommend:
https://kentcdodds.com/blog/application-state-management-wit...
https://redux.js.org/faq/organizing-state#do-i-have-to-put-a...
We're currently working on a major rewrite of the Redux core docs, and will emphasize this thought process in more areas of the docs as we go.
Client state management is all global no matter the strategy. ie Component level stores are effectively namespacing a global space with more cruft.
When you get into sets of components, the convenient store gets ugly/brittle no matter how you do it, so might as well keep it in a single store.
Step 3 is key. Step 4 is also important.
Then try to simplify the app as much as you can. A system has always 2 kind of complexity: the intrinsic complexity and incidental complexity. "Smart engineers" like to show off their intelligence. That usually end up making their life harder by adding a lot of incidental complexity that eventually lead to fragile architecture and frameworks that add more complexity than necessary.
Mobx at least leads to a more concise codebase.
GraphQL and Relay/Apollo/etc help a lot to reduce the business logic in the front and code to written in general.
- Our new Redux Toolkit package (https://redux-toolkit.js.org) simplifies most common Redux use cases,
- Our new Style Guide docs page recommends patterns that result in simpler code, such as using the "ducks" pattern for single-file logic ( https://redux.js.org/style-guide/style-guide#structure-files... )
- And we're working on a docs rewrite that will update the tutorials to show simpler approaches as well
The best way that I found so far are the principles of Object Oriented Analysis and OO Design ( https://en.m.wikipedia.org/wiki/Object-oriented_analysis_and... ) to break down the system in autonomous subsystems that make sense.
Generally the best philosophy is keeping things as simple as possible and refactor as new complexity is necessary.
How could anything else be true?
You might have a modal visible in the page, but the only indication code-wise that the modal existed was in the DOM itself, because there was a one-time mutation of the DOM via `$("#my-modal").dialog()`. The only way any part of the app could _know_ if that modal was being shown was to check on the actual DOM itself, like `$("#my-modal").is(":visible")`. Then you start getting into weird bugs because maybe some other script has mutated the portion of the DOM you cared about, or you're having to write logic to handle if the DOM _was_ in this state and now needs to be in _that_ state.
What we've learned over the last decade or so is that while that is fine for adding small amounts of interactivity to the page, it's not a maintainable way to build large-scale application codebases.
Instead, you want to have an actual `isModalVisible : true` boolean be the real source of truth, and the UI should just be a reflection of that value.
I'm not saying that React invented this idea or that it's exclusive to React, just that React's docs and community has generally put a stronger emphasis on that specific mental approach to designing the app:
https://reactjs.org/docs/thinking-in-react.html#step-3-ident...
The "state problem" that exists in React, Vue, Flutter etc. is due to their maintaining components in a hierarchical tree. It is a manifestation of a "cost" (drawback) in that tree-based approach.
React represents elements as encapsulated components that can be composed into more complex elements, which are maintained in a hierarchical tree.
That's the main attraction. One can reason about independent pieces representing some aspect of UI. Those pieces can be building blocks used to build more complex things.
The price for this is that how these (encapsulated) components communicate and share data between them is problematic because of that hierarchical tree structure, where an element only knows its parent and child(ren).
Wait, what? Hell no. This is literally the core use case for CSS classes. And while there are cases where CSS classes can cause problems, this isn't one of them. This is a case where CSS classes shine.
This is like an article on how to use fire to solve problems, and instead of choosing an example like staying warm or cooking food, the author leads with an example like disposing of tires by burning them in the rainforest. It's just an unambiguously bad idea.
There were a bunch of places where I wasn't familiar with the tools he's using and am curious, but I'm certainly suspicious of anything he suggests after the "use global state to reinvent CSS classes via JS" suggestion.
Maintaining state isn’t that much harder.
There are some cases where history is a bit trickier to get right, like infinite scrolling. Some apps have it working, although with varying levels of success in my opinion. The forum software Discourse does a pretty good job: https://discuss.emberjs.com/t/is-the-six-week-release-cycle-...
2) TypeScript is the only sane way to use it. The bouncing between files and "wait, what was the shape of my state here?" crap is intolerable without it. I mean that's broadly true of all Javascript but it's very noticeable with Redux. This is doubly true if you're working on a team.
3) I've had a lot of luck abstracting Redux behind a broader client library of some sort. You can expose the redux store itself and let the using code "compose" it as usual, but stick most of the "action creator" stuff behind the wall of the library, minimizing and moving to a more suitable level of abstraction the surface area the calling code is exposed to. This is especially nice if you may use the code in some environments where you don't want to or can't practically use React—you can still easily re-use the Redux portions, which is wonderful. Also tends to leave you with state code that's easier to test in isolation.
4) One of the big limitations of it is that you can't reference one part of your state from another. You have to copy, because of how Redux automagically detects changes to the state tree. This has been a lot more annoying and limiting than I thought it would be at first—turns out I need/want to do that more often than I thought I did, and the more complex the app the more I want to do it—but you've either got to learn to live with it or have some kind of external, supplemental state management alongside Redux.
Specifically, I like to be able to interact/test with my application as I build it in the console. I have had much success with wrapping my "Action Creators" in a API of sorts and mounting it to the `window`. This saves me from having to either create some sort of one-off component to "get at" some piece of functionality or hand-rolling actions in redux dev tools.
The terminology exists because Redux was originally designed as "just" another implementation of the Flux Architecture. Both "actions" and "action creators" were terms that had already been introduced by Flux [1]. There was considerable debate during the initial design phase about what terms to use, and the conclusion was to stick with Flux terminology to match the target audience of the time [2].
Note that the new Redux Style Guide docs page specifically recommends "modeling actions as 'events'" [3], using the "ducks" pattern for single-file Redux logic [4], and writing Redux logic using TypeScript [5].
In addition, our new official Redux Toolkit package [6] is our recommended approach for writing Redux logic. It has several utilities for common use cases like setting up the store, writing immutable updates, and creating slices of state, and it's written in TypeScript with an API designed to minimize the amount of type declarations you have to write. (In fact, I've even used its `createSlice()` function to write some fairly complex reducer logic that I used with React's `useReducer` hook.)
Finally, you _can_ reference multiple parts of the state tree in your reducer logic, but you have to explicitly set that up yourself [7]. The `combineReducers` utility is meant for the standard use case of defining update logic by domain "slices", and it's up to you to write custom logic if you need more specific behavior than that.
[0] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
[1] https://facebook.github.io/flux/docs/in-depth-overview#actio...
[2] https://github.com/reduxjs/redux/issues/891#issuecomment-147...
[3] https://redux.js.org/style-guide/style-guide#model-actions-a...
[4] https://redux.js.org/style-guide/style-guide#structure-files...
[5] https://redux.js.org/style-guide/style-guide#use-static-typi...
[6] https://redux-toolkit.js.org
[7] https://redux.js.org/recipes/structuring-reducers/beyond-com...
> Finally, you _can_ reference multiple parts of the state tree in your reducer logic, but you have to explicitly set that up yourself [6]. The `combineReducers` utility is meant for the standard use case of defining update logic by domain "slices", and it's up to you to write custom logic if you need more specific behavior than that.
That's cool though.
As part of that, we'll be adding better explanations of terminology.
(and by "we" I mostly mean "me", since I'm not getting a lot of help from the community with this task atm.)
Try Redux; acemarke has been evangelizing Redux Toolkit [1] recently and it looks great for a beginner not familiar to the Redux ecosystem.
But also - if Redux just isn't clicking for you, or if it is but you feel like writing it is unnatural - know that you're not alone.
Learn MobX, learn how to manage state with context and hooks [2], learn Apollo Client.
Yes it's a lot of up-front work, but you really do need to find a state management approach that works for you if you want to write non-trivial apps, in my opinion.
[1] https://redux-toolkit.js.org/ [2] https://kentcdodds.com/blog/application-state-management-wit...
Redux seems to be very polarizing about whether it increases or reduces complexity. As a back-end weenie who finds virtually everything about the front end confusing, I found Redux to be a breath of fresh air. Finally something straightforward that made sense and where the complexity of the code never seemed to exceed the complexity of the problem it was solving. But I was working with people who could run rings around me in the big React codebase we were working on who said Redux was a total mindfuck for them and they would never use it again if they had a choice.
Redux is great when needed, but can easily over-complicate something that can easily be solved by Context.
You may be interested in my suggested resources for learning React and Redux:
https://blog.isquaredsoftware.com/2017/12/blogged-answers-le...
https://blog.isquaredsoftware.com/2017/12/blogged-answers-le...
https://github.com/markerikson/react-redux-links
Also, please check out our new official Redux Toolkit package. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It's our recommended way to write Redux logic:
Meanwhile, we're working on a major rewrite of the Redux core docs. I'm hoping to put together a new "Quick Start" tutorial page that will show how to use Redux Toolkit and React-Redux in a "top-down" approach as a fast way to get productive. The current tutorials take a "bottom-up" approach, teaching how Redux works from first principles. I also want to rewrite those, keeping the same teaching flow, but updating the content to be easier to understand and to teach patterns that result in simpler code.
It's really easy to work with, and the concept of hierarchical state store is something that I haven't seen in any other library. If there was I probably wouldn't end up writing Statium in the first place. :)
Redux also has a reputation for being annoying to write because people cargo cult it into any project regardless of scope and because utilities like the excellent `redux-toolkit` weren't and still aren't as widespread as they should be.
hey, I'm trying to broadcast it as much as I can :)
fwiw, the adoption curve is definitely going up:
https://npm-stat.com/charts.html?package=%40reduxjs%2Ftoolki...
And the growth of the RTK package is especially impressive given that we renamed it from "Redux Starter Kit" to "Redux Toolkit" _after_ we'd released RSK 1.0.
We'll emphasize RTK even more as part of the ongoing Redux core docs rewrite. My goal is that it ultimately becomes the default way to write Redux logic, in much the way that the Apollo folks tell you to use `apollo-boost` and React devs default to use Create-React-App.
It's been a lot easier to comprehend coming from C than the React options.
https://github.com/joetex/flatstore/
Most of the difficulty with Redux is managing actions and reducers.
Out of the box, flatstore lets you get/set from anywhere, with component "connect" function to trigger re-renders on data changes.
It's probably only suited for smaller projects, but now you can prototype at the speed of light.
You do have some "concept duplication", more than code duplication. Indeed you'll have to define your interfaces twice (Django models and Typescript definitions for example), but that's always the case if you consume an API (you always hope that what you expect from an API is what is received).
It makes your website have the same development tedium as a mobile app but without any of the prestige or profit potential. Avoid SPAs at all costs. More companies are starting to ditch them and only have websites as advertisements for the native mobile app (CashApp and Venmo off the top of my head).
I’m talking about this desktop mvc adapted to hyperscript approach:
class AppCR extends CR {
ctor() {
this.db = new RemoteDB
this.sidebar = new SidebarCR
this.content = new ContentCR(db)
this.val = {a:42, b:3.14, c:this.db.fetchC()}
}
render() {
let {h, val} = this
return h.div.wrapper(null, [
this.sidebar.render(),
this.content.render(),
h(AuxViewType1),
h(AuxViewType2.model(this.val, "c")),
h.div(val.b),
])
}
}
class AuxViewType1 extends View {
render() {
let {h, cr} = this
return h.span(cr.val.a)
}
}
class AuxViewType2 extends View {
render() {
let {h, model} = this
return h.model_input({model})
// model == {object, key} (for "input")
}
}
Your controllers localize data, speak to the model in a non-ambigous way, provide data for views directly (no props, think view.cr and view.model works at any depth) and integrate other controllers by creating/destroying them explicitly and rendering when they are needed.You also may have a global read-only (mostly) store of reference data, but that’s more for caching large datasets, not for state sharing.
Btw, model_input could wait on a val.c promise and rerender appropriately without your intervention (something React folks were to deliver last year, supposedly with help from async setstate process, which was a requirement for that (why?)).
All of this React thing honestly feels like Haskell sneaked into an enterprise for the sake of “so cool we did it” giggles. Dan’s comments on this did not help much when I read ‘em.
Using a global store makes sense when your todo list evolves and the component storing the data is no longer mounted / exists. Examples could be:
- Full page individual todo edit - Separate component / page that uses the same data - Some background logic that does X based on the todo data
- [0] https://www.robinwieruch.de/react-state
- [1] https://www.robinwieruch.de/react-state-usereducer-usestate-...
>https://old.reddit.com/r/javascript/comments/f1jop9/6_things...
Other tools and frameworks are all optional. I’m a frontend dev and I never used many of the tools he mentioned.
Most of the use cases I can solve with that, the rest is usually either forms (covered by formik, final-form etc), async loading of data (Apollo) or local state and animations.
Otherwise I’d put all data fetching into hooks which can give any component which uses it access to all kinds of information and the ability to conditionally render.
I have found this pattern to be cleaner than using component lifecycle methods. An example can be seen here:
https://github.com/deanc/buyxiny.com/blob/master/site/src/co...
Follow-up specific question: In which cases is it better than react-easy-state[0]?
I am using react-easy-state on my company and on my side-project web apps. For me at least, it seems to add much less cognitive overhead than Redux. It is simpler to understand, simpler to use, simpler to read and maintain.
Am I missing anything? Because react-easy-state seems very underrated in my opinion.
- In 2014, a year after introducing React, Facebook announced the "Flux Architecture" concept [0]. Since it was mostly just a pattern and not a library, over the next year the community created dozens of competing Flux-style libraries, which I've referred to as "The Flux Wars" [1].
- Redux came out in the middle of 2015. Its design took inspiration from many of the existing Flux libraries, but then added some Functional Programming principles on top of that [2]. Many React devs concluded that Redux was the better Flux implementation, and several of the other Flux library authors promptly stopped working on their libs and began recommending Redux.
- Redux was designed to work well with React from the beginning, including the design of the React-Redux API [3]. Among other things, React-Redux made use of what was then a little-known React feature called "context", which enabled users to avoid prop-drilling values all the way down the component tree. In addition, the initial sales pitch concepts of "time-travel debugging" and "predictable state updates" appealed to many developers who had seen problems with libraries like Angular and Backbone.
- Along with all that, Dan Abramov had already picked up a bit of a reputation in the React community from his work on things like React-DND and some Flux-related blog posts.
Once Redux hit an initial critical mass, it became self-sustaining. People in the community assumed that if you were going to use React, you _had_ to use Redux. Tutorials were written that taught both of them together (along with setting up Webpack and Babel from scratch). Now you've got bootcamps teaching beginners React and Redux at the same time (which is unfortunate, because we recommend that people should focus on learning React first, and only tackle Redux once they're already comfortable with React).
Today, there's certainly plenty of other good options out there, with varying tradeoffs. My own estimates are that around 50% of all React apps do use Redux, and overall absolute usage is continuing to grow [4].
More recently, our new official Redux Toolkit package has been designed to simplify many common Redux usage patterns, and is now our recommended approach for writing Redux logic [5].
[0] https://facebook.github.io/flux/docs/overview/
[1] https://blog.isquaredsoftware.com/presentations/workshops/re...
[2] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
[3] https://blog.isquaredsoftware.com/2018/11/react-redux-histor...
[4] https://blog.isquaredsoftware.com/2019/03/presentation-state...
I’ve heard “yea, you can use Redux, but don’t” a lot.
Executing well includes building a great product, but it also requires marketing, which the Redux community has done a great job of to the point where it's the de facto solution.
Isn't that the definition of a unit test?
Unit tests are for testing individual components of your application (web or otherwise)
e2e or integration tests are for testing your application component/lib/api/whatever boundaries
https://facebook.github.io/flux/docs/in-depth-overview
Redux was originally designed as a Flux implementation:
https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
This is so incorrect. MVC doesn't imply two-way data binding. The whole Flux architecture was based on a misunderstanding of MVC, and is therefore questionable.
Most UI frameworks, including iOS, ASP.NET Core, JSP and JSF (Java based frameworks), Ruby on Rails, and Django (Python) are all based on MVC.
Why is it that MVC works for all these other frameworks, but doesn't for React? The answer is that MVC does in fact work for React. React itself originally used the tagline "React is the V in MVC". Flux/Redux introduce needless complexity and unnecessary boilerplate. There are MVC libs that make React much easier to use.
Don't all of those have two-way data binding?
> Why is it that MVC works for all these other frameworks, but doesn't for React?
I think the point is that Facebook found it doesn't actually work well, at least for their use cases. They found a recurring set of bugs caused by two-way data flow, and built a framework to alleviate it.
No they don't.
> I think the point is that Facebook found it doesn't actually work well, at least for their use cases. They found a recurring set of bugs caused by two-way data flow, and built a framework to alleviate it.
But MVC doesn't mean two-way data flow.
I've seen so much back and forth on this issue during my time developing with React. Intuitively, it seems like the URL should be treated the same way that DOM elements are: as a source of actions that may or may not result in state change - in addition to a reflection of state.
I agree that libraries for certain things, like MobX/Redux or Formik, may be overkill.
What's more important, though, is that people choose an appropriate equivalent to these libraries. Replace the library, but not the method of organizing state.
For example, feel free to replace MobX/Redux in my post with your own global state management solution. But I recommend replacing it with a global state management solution, not something else.
Use it as a store or cache that alerts all your components of data changes, works well enough.
Immediately I disagree and feel antagonistic towards this article.
Then the author provides an example of "prop drilling". Sounds an awful lot like Inversion of Control [1]. I've been down this road before. Next comes a Services pattern [2] (the world re-invents COM once again) and then comes Dependency Injection [3]. What he calls "global state" others have called "cross-cutting concerns" [4]. Pretty soon we'll be all aspect-oriented-programming [5] up in here.
> What you need to do is store your theme setting in a Redux or MobX store, or in a plain JavaScript object, and pass it to all your components using Context.
Yup. The beginning of a services pattern by changing to inversion of control. Once you realize you don't want all of your state on a single object you'll have `context.getUser` and `context.getTheme` and then you'll think having specific functions is a pain so you'll have `context.get(ContextType.User)`. Then you'll have interfaces so you can easily unit test with mock services.
This is all good stuff. Keep going down that road.
1. https://en.wikipedia.org/wiki/Inversion_of_control
2. https://en.wikipedia.org/wiki/Service_layer_pattern
3. https://en.wikipedia.org/wiki/Dependency_injection
4. https://en.wikipedia.org/wiki/Cross-cutting_concern
5. https://en.wikipedia.org/wiki/Aspect-oriented_software_devel...
All logic in the page/screen components and the prop-drill them down to where they are displayed.
I see the appeal of the simplicity of this approach, and this is how I handled state in my application before I knew what state management was.
But how do you deal with the problem of many of the components in your tree needing to know what theme is set?
Root - Page/Screen - UI Elements
The Root takes care of authorization and navigation logic, to switch between pages.
A Page has all the logic needed for one page it holds all the state and passes it down via props.
A UI Element displays the state it got via props and sends up its events via handler function (it also gets them via props)
In 90% if cases this was enough.
Sometimes, I needed to pass some state from the Page up to the Root and from there down to another Page after navigation, but that was it.
To be clear, this article is not useful. If you are trying to learn about state management in React, do not read it. Read this one: https://kentcdodds.com/blog/application-state-management-wit...