Single Page App. Simply: the application is loaded once when you navigate to the page and more ajax calls are used to retrieve data, perform actions as you use the app.
Single page application. A website where the first request loads the site, and most/all subsequent clicks make http requests in the background without reloading the whole page, and then update the content using JavaScript.
I had actually never seen that written solely as SPA even though I've worked as a react dev for 2 years now in Canada.
I guess the acronym really is going obsolete.
If you have Javascript disabled, all you should see is a blank white screen, and if you're lucky a small note saying 'This site requires Javascript to work correctly'. That's what a SPA is
When you are adding a new framework to render your single page app on a server, that's the point to stop and re-evaulate your technical decisions IMHO.
Don't regular web sites do exactly the same? They also usually pack all JS and CSS into two big files in order to reduce the number of requests on the initial load, right? And beside in SPAs you can split the code and lazy load it to reduce the load.
In the last Angular app that I inherited every menu on the page needed a separate call to populate it. It probably wasn't the best written app. I think if it had been plain Django with some jQuery for front end validation the size code could have easily been cut by a half or more.
React is about components and you can use React in something like NextJS which is not a single page app but a server rendered pages.
Also, SPA itself is getting outdated concept, now most apps are hybrid of initial bundle followed by a dynamically imported modules. Finally, MVC is a pattern for web pages, the complexity in the client side because what is the being shipped to the browsers are a client side apps with their own router, render functions, state management and APIs.
yeah there are a few ways to reduce the initial load time, but my point is it's valid to compare the initial load to every page in an SSR (for most use cases).
> Once a SPA is loaded you can send tiny crumbs of data faster than any significant SSR.
You can, but often that's not what's happening.
E.g. crates.io is currently built as an SPA. It is first receiving a HTML page that loads a javscript file, that performs an XHR request (actually, multiple) that answers with JSON that is used to create the final DOM of the page. The Javascript file is cached but that's about it, it still involves one additional needless roundtrip for the website.
The JSON sent is so badly designed that it alone is far more than the final DOM of the page.
Once it's loaded. But not all users have a fast Internet connection, even in the USA. So I'm not sure you can expect them to wait for your 1MB javascript bundle to load.
...which still can take significant amounts of CPU and RAM, especially on busy and underpowered hardware that webdevs don't tend to test their apps on.
There are lots of applications which don't really benefit from being an SPA (blogs, books, etc.). As soon as you start thinking about meaningful interactions you quickly start having to pile enough JavaScript into the HTML that you quickly run into a mess - defining ways to marshal data back and forth with an ad-hoc structure.
While SPA tooling like React are incomplete since they don't provide all of the client/server data communications that comes for free with a server side application. That is starting to become more "standard" as GraphQL and Apollo start filling the gap of boilerplate. Though of course wrapping your head around GraphQL takes a bit of work for anybody who's spent their life with REST.
Most applications should be SPAs to create the interactions that users expect.
This builds an implication that spa tooling is the way to avoid a mess.
Which is ironic to me. As soon as the teams I've seen start piling a ton of extra stuff into the UI, be it types to make things more safe or logic to make things feel faster, you get a mess.
Note I'm not claiming either of those are the problem. More code is just almost always more mess. Keep it small. If possible, keep it separate.
There are two forms of mess in my experience, none of them have to do with SPA.
1) You have the problems that different developers have different styles. So when you jump from one area to another in the code base you're hit with WFT this is functional/procedural/inheritiance/composition, etc. style and your first thought is "barf" they did it wrong...
2) People either have huge functions that are spaghetti or tiny little functions to avoid complexity that are one line if statements.
Both of these are usually rooted in lack of a shared code culture, these are not SPA issues but team issues.
These could also partially be language and tooling issues.
There are some languages that are so “powerful”, “flexible”, and unopinionated that every developer has dramatically different coding styles (JavaScript, Scala, etc.). On the other end of the spectrum are tightly prescriptive, constrained languages where everyone’s code looks the same (Go comes to mind).
No doubt team culture plays a part as well, but in team settings it is important to pick tools that are designed for team settings. These tend to prioritize regularity, lack of flexibility, syntactic simplicity, static analysis, etc.
Oddly, I would sum up both of those as bikeshedding problems. I agree it is a team issue.
I know the world is sick of gardening metaphors, but I do feel like there are lessons from community gardens. (Really any community thing.) You will usually have an owner that will paint broad strokes of what the rules are. Then, a bunch of spots that contributors are touching regularly. In their contributions, the rules are much more free form. But, you don't typically have people moving rapidly between contribution zones.
Which gets me to my favorite take on the answer. Solving the customer problem is not necessarily the same as solving any developer problem. If you are lucky, you can align them. However, as a developer brutal honest with self in "did this change actually provide any value to the customer" has to be constantly asked.
For CRUD apps, using Rails etc. with Turbolinks[1] is the best way to go IMO. It gives you the SPA feel without the headache. In all SPAs, we load JSON, with Turbolinks, we load HTML of that page alone (without the CSS, JS etc).
It is the best way to do what ? because from what I understand turbolink is giving you nothing. The only thing you can have is activating a a loaderbar at the top of the page ...
Turbolink is a crutch for people that are not able to server side render an html in a few milliseconds.
It's fetching and replacing the HTML that needs to be changed, but removes the need to do a full-page reload, so the browser doesn't have to re-load and re-parse all of the HTML, and JS, and CSS. Even though the browser will keep a lot of this stuff cached, Turbolinks can make a site feel a lot faster from a user's perspective. But regardless, it's more than just a loading bar at the top of the screen.
Whether it's the right fit or not depends a lot on what you're trying to do.
this is not just one simple drop in replacement, well it is if you load only html, but pages are usually html + js, even for "old" mvc. so because there is no page relaod you have to rethink all your events, objects and so on.
so if i have to rewrite my js with this just so i could fake SPA then benefits are so much smaller
I went from MVC style all-in-one frameworks (Rails, Laravel) to application APIs fronted by single page applications and hope the developer mindshare tilts back to MVC frameworks. Something that'd take me an hour or two in Rails can take a week using these new tools, even after being reasonably experienced with them. Don't even get me started on Apollo (not my thing at all, and from my experience most don't know how to use it well, including myself).
Most apps are fine serving html, using plain old boring forms, and having the odd React or Vue component where necessary to do some heavy lifting for user interactions. Moreover, it's a lot quicker as a small team to iterate.
I hope we don't go back to MVC for everything. I think we just have to use the right tool for the job. If an app is highly interactive or will have frequent data updates, or if it's supposed to be used almost like a native app, a SPA is the perfect way to achieve this. If it's going to be like most sites, traditional MVC will probably be better.
Not that react isn't great for traditional websites, I've just seen too many fullstack or backend devs struggle with it, so if they're going to be involved in the frontend it may be worth considering the alternatives.
SPA isn't automatically the best solution for a page or site with frequent updates.
The updates are handled by JavaScript and http calls.
You can write a lot of that stuff in a dozen lines of JQuery with nicely refreshing content and ajax instead of throwing in large amounts of framework bloat.
jQuery 2.1.3 is 27.8Kb. React + React DOM is 35.6Kb. 7.9Kb extra isn't nothing, but it's not something many sites really need to stress over. It's definitely not enough to state using React is adding "large amounts of framework bloat".
2mb is a LOT of code, like an absurd amount for most projects. That almost always points to larger issues, like a complete disregard for bundle size in the first place.
And if the project isn't worrying about bundle size at all, then even their vanilla/jquery page is going to end up bloating a ton as well.
More often than not the culprit in 2mb bundle sizes is a a few packages that include "data" in the bundle (For example, i've seen timezone and locale information bloat bundles by megabytes, and in one case a 5mb bundle ended up being 4.6mb of zip codes...)
lol I did it too once! We were making a PoC one time for a small app, and ended up needing a way to quickly determine some location information related to the zip. I remembered that I saw the ~5mb bundle once that had all the zipcodes in it, and found a library that would let me do that, and bundled it right in.
The PoC ended up working out, and we built the infra to correctly handle the lookups without needing to bundle the entire country from there, but man did that raise some eyebrows from a few other devs that noticed it!
Having used the internet for 25 years I can confidently say most React apps are far smaller than most jQuery apps, mainly because jQuery apps didn't use compilers or bundlers. Hell, most didn't even use minifiers.
Even ignoring that, React app sizes are getting better. Modern React apps (basically since 16.7) that are written with things like Suspense, lazy, and hooks are usually pretty small. Writing functional components and composing your app pushes you to write less code. Plus React gets things like code splitting for free with webpack if you use lazy, so the first page load only downloads things that are actually needed to get to interactive.
No doubt some less considerate developers will still manage to write giant apps that take ages to get started, but they don't have to. Bloated apps are a function of developer's choices rather than React (or any other JS framework) forcing the apps to be bloated.
> Modern React apps (basically since 16.7) that are written with things like Suspense, lazy, and hooks are usually pretty small.
Come on, 16.7 was released in December and 16.8, with stable Hooks, was last week! It's interesting that React has added these features and it bodes well for the future but you can't claim capabilities that have only existed for the a few months is what constitutes "modern." React's pace of adoption has been remarkable but that also means there's a lot of code that already exists that isn't going to be changed right away to take advantage of these features.
These features existed in the form of 3rd party libraries like react-loadable for code splitting and recompose for hooks. I don't know any serious React devs who haven't used code splitting in the past few years, and recompose was very popular as well. We just have a canonical way of doing these things now.
> You can write a lot of that stuff in a dozen lines of JQuery with nicely refreshing content and ajax instead of throwing in large amounts of framework bloat.
OTOH jquery is much bigger than you'd expect, there isn't much of a difference between a minimized jquery and vue, or even react once you factored in gzip.
And while I've no experience doing so using vue, writing small dynamic react components embedded in larger static HTML pages is quite enjoyable.
It's 2019, most of the stuff that jQuery used to handle is now included in the browser API. For instance, to have "AJAX", simply try window.fetch instead of $.get.
Then add a polyfill per api call to support anything but the most recent browsers, if you happen to want your site useable by people with old systems or who are at work.
For a component here and there React is a pretty bad choice because of its size. Svelte could be a more suitable option, but I’m not so sure it passes the magpie test.
In my experience this is the use case where vue shines. It's far easier to drop it into an existing html template than to create a react application for a single component.
The article has a reference and link to [1], where the behavior of constantly looking for new and shiny tech is likened to that of a magpie. As Svelte is still fairly new and not widely used, I think maybe it is in that class of technology.
I recently started a hobby project and initially went with a JS solution. But when I had to start deciding on auth, maybe an orm, routing, db layer, and templating, I said fuck it and installed Rails.
Previously I had never built a real product in Rails, only the book store tutorial. I'm really happy with Rails so far. I don't have to worry about a lot of shit I had to worry about in JS land and now I can focus on the app itself.
This is my experience as well. Sometimes the conventions are frustrating, and particular to Rails I still don't know how to handle the front end well aside from jQuery (how are you doing it? Stimulus + Turbolinks, or...?), but is it ever refreshing to just focus on your product instead of all the tooling surrounding developing your product.
If you need a lot of JS, Vue is a nice choice. You can drop it in for just a single page of your site if you need heavy JS on one part but still want Rails on the rest. Or you can go full-on Vue front end with Rails in API mode, or anywhere between.
As you're doing Rails I recommend you check out Stimulus JS (https://stimulusjs.org/). I've installed it in a recently project (Rails 6, rake webpacker:install:stimulus) and it's like a breath of fresh air. Totally gets out of your way, but lets you add behaviour to your app as you need it.
Agreed. Stimulus has been great and very refreshing. I love writing my html using erb templates, and being able to do that and easily add client side interactivity has been great!
A SPA should be the emergent result of creating an application that does something people want, not a goal in itself. Your builder doesn't show up at your house to put on a new deck and go "Gee, I really hope I get to use this 20-pound sledgehammer on this job. I hear it makes great projects"
If you start with how it's going to end -- it's not going to end well.
Having said that, what you're most likely seeing is that a ton of tools and frameworks are built for no other reason than 1) Coders need something to create to show other coders how awesome they are, and 2) People want something that works like everything they know -- it just does these one or two new things
This is a great way to make a mess over a decade or two, and you're right for waiting it out.
Make a SPA if you need a SPA. Key question: How do I know whether I need a SPA or not? (Or whether React makes sense, etc)
What I did for a mobile web app I built last year after the react prototype was to slow on old devices:
- Render all pages/routes/states into a single html file (size ~2mb) but each page/component/state hidden via style=display:none;.
- then write some hand crafted js (few hundred lines) to add event listeners to forms and show/hide the components depending on url change
- cache everything via appcache/serviceworker
- result: not so fast initial load (1-2 seconds) but really fast interaction afterwards even on old lowend devices, almost no time spent running js/react/vdom. even some scrolbugs I had to work round before when using react (eg replacing dom nodes but keeping scroll position of a parent container) resolved on their own.
If you have zero of that content cached locally and are on a mobile or just not-fast connection it'll be way more than 1-2 seconds.
Your "solution" is one of the ones responsible for locking up mobile browsers all over the place, because the browser still has to deal with the entire DOM, and it's often done way inefficiently.
> 2mb html file, + whatever else for initial load?
nothing else because all images are already inlined as svg as well as all styles and the mentioned js code
> Your "solution" is one of the ones responsible for locking up mobile browsers all over the place, because the browser still has to deal with the entire DOM, and it's often done way inefficiently.
I only came up with doing it this way after previously having build the same app using react and old devices (eg android pre chrome) could not handle all the vdom diffing and add/removeNode.
Our users preferred a slightly longer initial load (even 10 seconds) and lightning fast interactions afterwards over fast initial load and sluggish UI for all the time using the app.
When doing client sit rendering you would still have to transfer and parse little chunks of json all the time.
As it turns out browsers are really good at handling even huges DOMs as long as you do not manipulate it to much.
Granted this way only works if you have a limited set of states/pages (eg a few thousand).
> built last year after the react prototype was to slow on old devices:
Do you have a public implementation available for review? I'm having trouble understanding why this would be the case (I am pretty familiar with React's implementation).
>Render all pages/routes/states into a single html file (size ~2mb)
This is one page/app I built that way [0]. In this particular case it's not even fully optimized (eg js and css not minified, loads some external images...)
Personally I was dumbfounded by complexity of doing async things in redux. Libraries like redux-thunk, redux-sage, redux-observable should not exist. The amount of boilerplate required was incredible and meaningful typescript support was very difficult to achieve.
Then I tried mobx, I was able to write a small app after 15 minutes of reading the docs and now I love react.
I've still found the concept of computed properties in mobx to be extremely useful (and I say this as someone who relies on regular React + SetState 90% of the times). MobX encourages you to think about representing your state in the minimal possible representation and to heavily use computed properties that depend on this smallest, irreducible state representation for everything else. Switching to this mindset makes developing more complex components (especially UI/forms) a breeze. I also find MobX to be great for async things and much easier to handle than via setState. Using setState alone in these types of complex components ends up being really tedious and prone to errors (largely driven by the async nature of setState updates).
Yup, MobX has made developing more complex React components / UIs a sane endeavor once more. Every time I use MobX, I think to myself that it can't possibly be this simple...then run the code, and pick up my jaw from the floor because everything just worked the way you intuitively thought it should.
Meanwhile, relying on setState alone for complex components ends up being very error prone and tedious. I still rarely use MobX for global state because I prefer the original appeal of React in terms of making modular, reusable components, but I often use MobX as the state mechanism for my more complex components and bypass having to rely on setState altogether for those components.
Also one of the things not listed here is the added complexity of an SPA, the routing bugs, the old browsers bugs, the added complexity of splitting js files so the initial load does not become too big, the data storage...
I'm currently maintaining alone two large websites and only one of them is an SPA, that's definitely the harder one to maintain by far.
I see the same thing happening on the front end that I saw happen on the back end with big data. Just because it fits better into Google and Facebook's Enterprise patterns doesn't mean it will work for everyone. Very few companies need Hadoop, probably as few as need react
To add to this, bandwidth and app size is not important to the user(they'll watch netflix later). The only thing they notice, is response time.
React with well defined static content routing and server-side rendering is amazing at this because the first packets that the server sends the user is html/css jumble.
You can get your app to show text and formatting in as little as 20ms(depending on where you deploy it), then show images, then you don't care if the rest of App and all the js and 3rd party crap take another 2.5s and 3mb to load, The user will do a bit of reading and picture watching, before using a button or input field. However if they're on the go and loose connection, now they have a fully functional back button in their browser to read the next or previous cached html/css content.
universal web app, that is web apps that can be rendered from the server or on the client are indeed amazing. Blazing fast loading on first use (like a traditional webpage), but also super fast to use for every subsequent actions (like a SPA). For a website like amazon, that can make a huge difference in revenues.
The price to pay on the other hand is complexity. It demands programmers that really know what they are doing, and even then, still has an overall cost you wouldn't find with an SPA.
Is it worth it? Well if you're a startup with 200 users, probably not. But for a big company, that can be a big way to improve results
The latest project I'm currently working on I got to choose how to build. It is an internal CMS. I went with Rails API, React, Material-UI. So far, I like it.
I like separation.
I know that one side ( ruby, rails, gems, pg, sql etc ) is all about data -> one mindset.
Another side is about views ( js, css, html, multiple select, date pickers, autocompletes etc ) -> different world.
I build my assets in one place, I work with my data in other. I wear one hat as a backend guy, and when I need refocus completely on front end work.
I wouldn't argue it's easier, but I think it's easier for me. I like it better.
Plus, the API will be consumed eventually by user-facing site and apps.
I'm almost exactly the same situation these days: small project, got to choose my tech stack, so I went with a "conservative" part I have good experience with (backend in Flask) and a more experimental part (for me) that I'm taking the occasion to explore and learn: React + TypeScript + Material UI.
For that latter part, although I must recognize that the tooling has evolved into a quite complex pipeline (which is a cognitive investment, almost a gamble, in itself), I also clearly see the benefits of a set of ideas that have rapidly evolved in the turbocharged settings of the web development ecosystem:
React: the distillation of many programming paradigms (functional, composition over inheritance, etc) for building UI components.
TypeScript: the augmentation of JS with mature and powerful programming constructs.
Material: a visual language for UIs with a strong focus on coherence and being truly cross-platform.
All in all, although I'm well aware of the critiques that can be addressed to the modern webdev environment (the so-called "JS fatigue"), I feel like all these new tools empower me, and I really enjoy learning about them.
> For those of you who use moment-timezone there is a trick to reduce the size (if you can get away with 2012-2022 dates)
Lol, a 3 year solution. I immediately feel sorry for the future devs jumping into a codebase like that on New Year's 2023 trying to figure out what broke overnight.
I don't use Elixir/Phoenix, but Phoenix's LiveViews brings all the butter to the monolith bread. State remains on the server, only visual state is sent over websockets, and the HTML is transformed with morphDOM.
I agree - it seems like a LiveView equivalent for Rails should be doable in principle. Check out these two projects, they're sorta heading in that direction:
Fie: https://fie.eranpeer.co/
AnyCable: https://anycable.io/
If you watch Chris Mccord's videos (the creator of Phoenix and LiveView), it was an attempt to build a similar system for Rails that drove him to create Phoenix. The Ruby/Rails concurrency model mean that he was patching over so many cracks to try and get it to work, and it never quite did.
We recently went in the opposite direction for a student project I'm working. We have a full SPA Angular frontend, and we use CouchDB as our datastore. There is a Node "backend," but it only serves things like Slack integration, the browser has a local PouchDB instance that directly syncs with the server's CouchDB. This way, we can deploy hotspots with nothing more than a Couch install and a static server for the SPA.
The modern, offline web is great. If we had to do this in a native app we would never have gotten the project off the ground, because we initially need to use BYOD phones which were evenly split from iOS/Android. We'd have had to use three languages, but a Mac, get developer licenses, and learn Swift and Java just to get an offline frontend, and sharing business logic code would be impossible.
This sounds interesting. I'm light on details on how CouchDB and its sync works, but how do you deal with data validation/data conflict? Or maybe malicious actors tampering your data? Or is it a closed app that already have those dealt with in the spec? It would be awesome if you can share more about the project.
I'll email you a copy of the whitepaper once it is finished. The famous quote is, "CouchDB is bad at everything, except syncing." It has a built in revision system that will create two conflicting revisions on both ends, and allow your code to implement the business logic to determine the one true revision. This could be a prompt, or a rule based on the data (ex newer timestamp winds), or even an average of various data fields to create a whole new field.
In my personal experience very few projects actually want or need to be SPAs, developers are just dying to work on SPAs so hiring managers oblige to hire talent.
After working exclusively on SPAs for a number of years (React, Vue, Inferno) I'm getting out of that unless there is a strong argument in favor such as a hybrid mobile app (and that will go away once we move into Flutter).
I don't want to go back to Rails/PHP/Python though. I like having a REST/GraphQL API. With Hasura I don't have to worry about backend anymore for 80% of the cases.
What I'm doing right now for the front end is using Jekyll with Vue. It's really liberating not having to worry about a router or managing application state anymore. For certain in-house projects I don't even have to use Babel or Webpack anymore and still get to use Async/Await.
I think the beauty of SPAs is consuming your own APIs, because it makes you realize that your website is just another client application.
Nobody worth their salt would recommend coupling display components and database manipulation together in an iOS or Android app. A webpage is the same thing, because it's not running on your server, it's running in the browser on the user's computer.
With this blog post, like most "I don't like X technology" blog posts, I find myself asking, "does the author not like this technology, or has he not taken the time to become a real SME in it?"
I guess another real advantage of SPA is that you get an API for free. Last place I worked, the product people had a genius idea to "build a public API". They decided to give us half a year to do it.
We had a SPA using OAuth.
I spent a few weeks adding docs, ran Swagger codegen, then declared it finished. Every call our front-ends used was now part of an API. Then we just trimmed out the parts we didn't want to officially support.
I come across so many sites that have been needlessly built as SPAs, and so many of those are broken in ways that are really hard to understand even as a technically savvy user. For regular people, it's a truly appalling experience - the sites my wife has to use for work are all like this, all broken and all slow - she blames her laptop. SPAs need to die.
I don't think it is, no. A plain old HTML and CSS website gives me some clues about what's happening when I click things: if I click and nothing happens I can see a progress bar in my browser that shows me that I'm waiting on network - if I do the same in a SPA I get no feedback at all. You could say that all SPA developers should be writing this feedback, but even that isn't reliable - quite often the broken SPAs _do_ show their own feedback with a spinner or similar, but still there's no way of knowing if it's actually doing something or if it's broken. I can open the web inspector and look for XHR requests, but if it's trying to do something with no XHR then again, no way of knowing. They break user expectations, and when they don't function correctly this can be hugely frustrating.
Back when I was doing React, what pulled me in was that it WASN'T a framework for SPAs. What I got from it was the ability to build UI widgets (components, in React terms) that worked within a page, and were composable with other UI widgets.
With that approach, you really wanted to keep every component self-contained. That meant a lot of bookkeeping with passing events back and forth between parent and child widgets, but while it was a lot of typing, it was all fairly easy, and if I wanted to change how an event was handled, it usually only involved a component and MAYBE the components immediately above or below it in the component tree. Data passed further than that was generally wrapped in objects and therefore didn't require code changes.
Then introduce Redux and similar frameworks, and I totally lost interest. It's as if the entire React community forgot all the lessons they learned writing Python and C programs in college and went back to creating global state. What in the actual hell? I wrote a good amount of code in Redux, too--I worked on teams that used it--so this wasn't just pre-judgment. These frameworks add a huge amount of hidden global complexity just to avoid having to pass parameters to components, which is the simplest thing to do.
And once you're doing that, you HAVE TO write an SPA, because any part of the page you want to be interactive gets pulled into Redux and your JS eventually eats the whole page. It's an infection.
When writing JavaScript now, I sometimes end up rolling my own version of the component style. It's an effective design pattern for UI widgets. But it hasn't been worth it to use React for a while now because the tooling and tutorials are all too conflated with Redux and similarly awful tools. And on a team, there's always the worry that someone on your team will infect your code with Redux and it will grow and consume your entire site. It's a real shame.
You've been lucky then. I've worked on a few teams (I'm a freelancer) and even when they didn't add Redux, it's been something I've had to argue against.
Server side rendering mounts your application in the server and sends down html along with an initial state.
The client receives this initial state and uses it with the html to glue together a working UI on the client.
This removes the massive JS bloat into raw html and split JS for each component.
Instead of sending 3MB of JS, you send only the JS needed to render the current page... and send additional JS as a user navigates through your website.
Okay, but that's an even worse idea than what I've seen Redux used for. Now you're splitting the UI between client and server in a completely custom way, that adds even more complexity. Worse, the performance is likely to be worse because now you're paying the bulk JS load cost of loading Redux AND the latency cost of loading small parts as you go: the worst of both worlds. I doubt this is what the Redux folks had in mind, but if it was, it's even worse than I thought.
Redux is tiny. And with HTTP2 it's better to send multiple small resources than one large payload, especially when it's JS which will lock up when parsing.
>because now you're paying the bulk JS load cost of loading Redux
Gonna have to quantify that. Cause I imagine if you did, you'd redact that statement. Redux is a very straightforwards and SMALL library that handles global state.
Its concept can be difficult to follow at first which may cause some to think it way more complex under the hood than it really is.
> Then introduce Redux and similar frameworks, and I totally lost interest. It's as if the entire React community forgot all the lessons they learned writing Python and C programs in college and went back to creating global state. What in the actual hell?
Fair point. But could it be possible that there are lots of people doing React that never went to college and never wrote Python or C programs?
I'm surprised you dislike Redux simply because it's a form of global state.
Sure, global variables make debugging difficult when you don't know which function changed them where and when and why. But that doesn't happen if you keep your functions pure and only make changes from within reducers – at least that's my experience.
(Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
Anecdotical comment: While studying computer science, I almost fail a test because I used global variables. Since then I avoid them like the plage, and I get rashes when I see them.
And I thank my teacher for knocking the bad habit out of me. So when I see global variables beyond the initialization of core objects, I take it as a bad code smell.
Global variables are different from global state. Every program has global state, whether you like it or not. Global variables are a poor way to declare, mutate, and read global state.
My intuition here is that what you're saying is true in a trivial sense, but that most state CAN BE (and should be) local if you're breaking your programs into reasonable building blocks.
You have information which has global nature, used in multiple sorts of the application. This could be, among other things
- STDIN/STDOUT/STDERR file handles
- static configuration
- some parts of runtime configuration
- application info (in a game maybe current score, current level, active players, ...)
All that information exists only once and is needed in many parts.
Okay, I think we're talking about different things. I don't think of stdin/stdout/stderr or static configuration as state because they don't change. Typically when I talk about state, I'm talking about mutable data that may change over the lifetime of a program.
stdin/stdout/stderr may be redirected during the lifetime of a program. The OS manages them and they are "reference pointers" (file handles) for multiple reasons.
Also, "state" does not necessarily imply "mutable". (Redux encourages an immutable state approach where good reducers don't mutate state in place, but instead build new state from old, though Redux is not strict in enforcing this best practice. For Redux the "current state" is "file handle" that simply updates from one object to the next, not all that different from stdin/stdout/stderr or even somewhat to static configuration that is somewhat updateable without restarting the application.)
> stdin/stdout/stderr may be redirected during the lifetime of a program. The OS manages them and they are "reference pointers" (file handles) for multiple reasons.
> Also, "state" does not necessarily imply "mutable".
Okay, sure. I'd rather not get into a semantic argument about the meaning of the word "state". Suffice it to say that I'm talking about mutable global state.
I think many people could argue that this concept itself is a bit misguided. There are plenty of application patterns and architectures that help you move stateful data around your app and translate it for the components who need to consume it or change it, while hiding it from components in the tree which do not interact with or care about the state.
If you have many different components in your app that all need access to the same state data, and they exist at different levels in the hierarchy, maybe you need to re-think your component structure rather than figure out a way to inject mutable global state everywhere?
Okay, technically it's copy on write, but I think if you put a bit of effort into understanding what people mean, you're smart enough to understand what the parent comment meant. Let's avoid making this holy war into a semantic argument. :)
Sorry to reply twice to the same comment, but if you have an instance of a user model, and you need to display the model data in the navigation bar, on the user profile page, and in a comment thread, how would you manage that data? And do you think that having a single source of truth reduces complexity or creates more problems than it solves?
There are definitely options and choices for all of this, but I can give my professional opinion.
First question (show user data in nav bar, user details page, and "comment thread"):
User profile page is obviously showing full user details, so you're going to want to have a separate endpoint for "getUserDetailsByUserId(int userId)" or however you want to name it.
What data are you showing in your nav bar? Is this just a user-avatar image link/button? You would rarely want to display anything other than an avatar and/or username in a navigation bar. You could maybe talk about a navigation drawer instead, where you have more simple details like maybe email address and phone number or something.
What about the "comment thread"? Are you showing anything more than an avatar and/or username? This sounds like the same use-case as the nav bar... how would the display differ here?
If you have use-cases for anything more than your basic "user avatar" and "full user profile," (and maaaybe "user basic info," for a "nav drawer" or something, although those are being phased out of most current design standards) then I would seriously urge you to re-think your app design. That polish of showing the user info in a slightly different slice on some specific page is almost never going to pay off for your company, imo.
Anyways, if you want to be naive about all of this, you just save the userId (and token or whatever, but that should be in your http middleware) after a new login session, and then you request separate "getUserNavBarInfo(userId)", "getUserDetails(userId)", and "getUserCommentInfo(userId)" and load the results of those into your UI views on-demand. If you need to progressively tune/cache your API, you do this separately based on usage stats for each of these 3 use-cases, you don't start with directly caching "Users" and adding the business logic client-side to translate into the 3 use-cases.
If you want to do a more thorough dependency-injection "user component," then this might vary depending on your DI framework, but the high-level idea would always just be to create a user module with all the info needed for a given user, wire it up to reload the module in your DI graph on new login and on-success of any request to update user info. You could have both your "login" and "update user" endpoints both return the full set of data needed for the "user module" on success. Then you need to set up your DI injection component scoping so that your user info injection component is dumped on user logout and settings change, and is recreated to do it's injections with the new/updated user module each time. A singleton DI "user component" that you must choose to inject into any UI component that wants to show user information seems much more constrained in complexity to me than just having the ability to arbitrarily pull out various bits of user info from a global redux state, at any place in your app.
Second question (single source of truth, increases or decreases complexity?):
I think this question is orthogonal to the capabilities Redux (and similar) provides. Redux has nothing to do with outright increases or decreases in app complexity, it is simply a tool that enables various (opinionated) architecture patterns. If you have a separate, domain-specific, well-defined event+data architecture that you are sticking to very strictly to keep a handle on complexity, then Redux can (imo) reduce boilerplate and allow very elegant interactions between various data/biz logic/UI components of your application, all without any "tight coupling". It's really just a message-bus pattern at some level. This sort of thing has been around longer than the concept of software itself, there are hardware components literally called "buses" that basically do the exact same thing (ship e...
I think these debates about global variables or state could be much more specific, though.
For example, we tend to realize that letting any component access a `require('./store').currentUser` singleton may make testing harder, but this global dependency still exists when, say, it's distributed to all components through the React context. I reckon most people are thinking of the singleton example when they lambast Redux but it certainly doesn't have to be that way.
Redux means everything is global state which is terrible. A grid I render now has to have global state and most projects forget to do any cleanup - so you end up with all this junk in redux that isn't needed anymore, clogging up memory.
Nothing is forcing you to put everything in Redux. If state only has relevance to a single component then keep it in that components state, Redux works perfectly fine with this approach.
And when the state has relevance to two components? Now it's okay to make it a global, because we can't be bothered to type "prop = value"?
I'm just not buying this argument. People add Redux to projects because they intend to use it, and there's almost never a case where a piece of data is actually needed in enough places that it should be global. It's certainly not common enough to justify importing a whole framework for managing global state.
> Sure, global variables make debugging difficult when you don't know which function changed them where and when and why. But that doesn't happen if you keep your functions pure and only make changes from within reducers – at least that's my experience.
Sticking to reducers IS a good idea and it does make things a bit better. The problem with global state goes deeper than that, though.
Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
With independent components, you do spend some time synchronizing data across components, but each component has the option of how to represent its data internally, so the effects of changing data representation in one area are usually very local, and each area of the code gets to represent the data in a way that's reasonable for what it's doing.
I'll also add that with independent components, you sometimes get components whose only real job is to manage data. That's actually sort of like a Redux! But you can spin up multiples of them without having to manage an array, and compose them or even nest them without issues.
> (Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
To be clear, I don't really care about boilerplate. It's easy to generate that stuff, but even if you write it by hand it's faster than debugging implicit, hidden complexity. Boilerplate ISN'T the problem with Redux, as far as I'm concerned.
If you're giving me the choice between dealing with this:
> Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
or this:
> With independent components, you do spend some time synchronizing data across components
I'm not even hesitating before I choose the first option. My editor, static analysis, and the mystical art of the function can give me all sorts of help with the first problem. Good luck with those sync bugs.
You selectively quoted me, ignoring where I explained that synchronization isn't as big of a problem with passing properties into components. I already addressed your concern. Don't selectively quote me, it's rude and just shows you aren't following the conversation.
Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way. In the most complicated situations, it actually exacerbates the issue because it separates the data from the components that use it, making static analysis tools unable to see how your data changes might break the components. This also goes to show you didn't even understand the part of what I said that you selectively quoted, since I already said this.
> I'm not even hesitating before I choose the first option.
Perhaps if you hesitated a bit more, you could use that time to read all of the options and understand them.
I read the thread. You hand-waved away a non-trivial problem by "explaining" properties are a thing. Thanks, bud. I'm sorry I didn't cite your favorite source to your satisfaction, but I quoted the relevant parts of the post I replied to.
> Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way.
It's kind of the whole Redux philosophy that you won't do that. It's in the FAQ. There's no "can't". You're never forced to keep duplicate data in your store.
But, in a project of any significant complexity, you will represent the data in different formats, because transforming data into different formats is one of the main functions of software. Relying on a framework that assumes you won't do something that you will do is a Very Bad Idea.
It's almost like `mapStateToProps` is a thing and Dan Abramov has built a web app before. You have to store and transform your data in a normalized form, but you can consume it however you want.
Restrictions are the price of solving hard problems (even if you don't want to admit they're hard) in a deterministic fashion.
> Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way.
I would argue that synchronizing data between multiple representations is an anti-pattern, and the exact anti-pattern that redux, with its emphasis on a single source of truth, was designed to solve. Synchronizing state is almost always going to lead to hard to diagnose bugs in edge cases. There's even a blog post on why it's a bad idea on the official react blog [0]
My solution to this problem is to use computed values generated by a library with memoization like reselect. I structure my data as close as possible to the API data models and use selectors to transform the models into the data structures that disparate UI components require. If you need to change your UI component, you don't need to refactor your store, and if you need to change your store structure, you don't need to refactor your UI components.
It's generally considered a best practice to use pure selector functions in your UI components so the only place in your application that references the actual data structure is the root selector that pulls data out of the state tree. I personally try to structure my selectors so that any refactoring only requires changing a single line of code.
Okay, so the only way it's actually possible to have any refactoring of your data structures require only changing one selector is that you've got a single selector for literally every piece of data in your system, at which point you've had to manually flatten everything out into a bunch of selectors, and lost all the benefits of being able to read a structured chunk of data. If you actually do that (which I don't think most projects do fastidiously) you're creating a lot of boilerplate, again to solve a problem which was created because you decided to use global state in the first place.
I'm not saying there aren't solutions to the problems I'm bringing up. I'm saying you don't have to solve them in the first place if you don't bring in Redux.
Not at all; you can write selectors that return a branch of your state tree, and you can generally tell where the line should be (user makes more sense than userName and userEmail).
You keep saying that you won't have these problems if you don't use redux, but I am arguing that _you do have these problems_, you're just creating ad-hoc solutions for everything that you would use redux for, except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs. Note that I have already agreed that there is a scale where redux is overkill and creates more problems than it solves. Once your app has a few dozen components it's time to start thinking about another layer of abstraction.
> Not at all; you can write selectors that return a branch of your state tree, and you can generally tell where the line should be (user makes more sense than userName and userEmail).
And then you're right back to any component that uses User being tightly coupled to the structure of the User, without any of the syntactic cues that allow you and your static tooling to connect the two.
> except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs.
That's not the case:
1. There's nothing preventing you from treating components as a pure function of their props.
2. At the top level, it's necessary to have some mutable state, but I'm not sure where you get the idea that this isn't testable. Redux doesn't remove mutable state: all redux is doing is a big complicated version of newState = pureFunction(oldState) which is perfectly possible to do using Vanilla JS functions.
One of the problems I noticed with Redux is that you need to clean your state. This is one of the reasons why global variables are a problem.
If you keep your state in a component, you don't need any housekeeping, once the component is gone, the state is gone.
This is not true with Redux. You have to keep remembering to clean the state after you're done with something or you're going to introduce a bunch of bugs.
> These frameworks add a huge amount of hidden global complexity just to avoid having to pass parameters to components, which is the simplest thing to do.
Redux offers a few benefits:
1. Allows you to keep your global application state 100% predictable by treating it as a pure function of an event stream (dispatched actions). This makes it trivial to test.
2. Allows you to "time travel" the state of your store using the Redux dev tools by replaying/modifying the event stream. You can also set up QA folks to export their events so that developers can reproduce any bugs in the global state 100% reliably.
3. Allows you to access global state without explicitly passing down props from great-grandparent to great-grandchild.
While I'm not a fan of using Redux for everything (it does have costs), you mentioned only the most minor benefit. If you only want #3, you would just use React's built in context API.
Redux definitely adds an extra layer of complexity to a project and a lot more boilerplate. That is what lead to Dan's famous blog post "You Might Not Need Redux" from 2016.
I have yet to see a long term project built with Redux that was maintainable. That seems to be the common denominator in React projects that do not survive technical debt accumulation.
The issue with Redux is that it encourages side effects in components. After a while people are dispatching all over the place and you end up in a similar situation as you were with keeping track of JavaScript events. All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
If you can convince and enforce that all Redux activity only happens at the root component, you should be okay. But the reality is that once you have the discipline to only mutate state in your root component, then setState tends to be more than enough for anything but very large and complex interfaces.
With that said this article has the author implementing their homebrew bespoke event system which is likely even worse. This was a very popular and much maligned anti-pattern during Angular 1.x days. This article is suggesting the absolute worst possible idea for maintenance.
What kills the maintainability of React and similar frameworks is tracking down what component did what. Unfortunately, in real world examples, in my experience, Redux increases that issue and ultimately leads to a very difficult application to maintain long term.
> I have yet to see a long term project built with Redux that was maintainable.
The only maintainable projects I have worked on in terms of web apps have been with Redux.
Redux is not great for simple projects, it is great for medium and large scale projects.
> All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
React, redux, redux-saga -> this is the recipe for large scale applications that I have used, built, and helped maintain with success.
One of the strongest benefits of redux is being able to see every single event in a debugger that displays current state, action payload, next state. It really does make tracking down state changes a matter of reading a global log.
Furthermore, because redux is not dependent on react and under the hood it is simply and event emitter, it becomes relatively easy to use the same exact business logic for one web app and use it for some other delivery.
We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
> Redux is not great for simple projects, it is great for medium and large scale projects.
I'd say that the only times I've seen Redux work effectively at all is when the project was small enough that the complexity remained manageable.
> > All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
> That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
I don't need a way to manage side-effects, if I don't use Redux...
I'll admit I haven't used Redux-Saga, but you'll excuse me being skeptical that introducing more complex tools will solve my complexity problem.
> We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
Again, this is not unique to Redux. It's perfectly possible to have your business logic completely separate from the UI layer without Redux. In fact, my experience is that Redux tends to cause devs to mix UI logic into business logic (and any sufficiently complex UI will have its own state and logic separate from the business logic).
> but you'll excuse me being skeptical that introducing more complex tools will solve my complexity problem
I'm a consultant and in my experience this misconception is by far the most common source of problems in redux projects. Redux is a very small and simple thing so you have to build a framework around it. Even redux-thunk isn't enough - that's only 5 lines of code.
I wouldn't recommend react/redux for public facing sites if there's no experienced, pure frontend team. A more batteries-included kind of framework would be better. There's a lot of thinking to do and hardcore levels of scaffolding to maintain before writing anything useful and maintainable with redux. But it can absolutely lead to maintainable applications.
Also, I've never seen any homegrown "vanilla" frameworks or data stores work well as complexity increases. It usually ends up as spaghetti, especially with a growing team + growing business demands. Or best case a poor re-invention of the wheel. There must be brilliant counter examples but they're probably rare, I may never have the pleasure of working with one.
FWIW, we've got a new Redux Starter kit package that tries to be a bit more "batteries included". It includes utilities that help simplify several common use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state automatically. It also includes `redux-thunk` built in, as well as some middleware that check for things like mutations.
Thanks, this is incredibly useful, I use everything in this package! It also doesn't deviate from the spirit of Redux or abstract too much, like rematch for example. Without something like this, I find that many people structure their entire Redux code based on simple tutorials. They typically only read the docs or worry about best practices and complexity after they encounter problems. This kit will have them start off on the right foot.
I understand why folks have opted to create higher-level frameworks around Redux like Rematch, and it's not "wrong" that they've done so. But, looking at Rematch code, you can't really tell that it's actually Redux underneath.
With RSK, I've aimed for an intermediate level - decreasing the amount of things you have to write by hand and the literal number of keystrokes you have to type, but preserving the idea of "dispatching actions" and using reducers.
Glad to hear that you think it looks good! If you've got any additional feedback or suggestions, please let me know.
> The issue with Redux is that it encourages side effects in components.
Which is why a lot of people that encourage Redux tend to also encourage one or more of the "side effect" managers such as thunks, sagas, or observables (my preference).
From my perspective, React is the thing with the problem: it doesn't have great state management for complex tasks / multi-component coordination. What I want to use is something like RxJS to manage such interactions and state, because it gives me a lot of power/control in a relatively easy way. One of the best options out there that I've seen is redux-observable, and thus Redux itself is just the "stuff inside the oreo" gluing React to all my interesting "epics" describing high level functionality in the applications.
It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
> From my perspective, React is the thing with the problem: it doesn't have great state management for complex tasks / multi-component coordination. What I want to use is something like RxJS to manage such interactions and state, because it gives me a lot of power/control in a relatively easy way. One of the best options out there that I've seen is redux-observable, and thus Redux itself is just the "stuff inside the oreo" gluing React to all my interesting "epics" describing high level functionality in the applications.
The observer pattern is mentioned in the Gang of Four Book[1], published in 1994. JavaScript first appeared in 1995. Observers predate Redux. You don't need Redux to use observers, and in fact there's nothing about React that is incompatible with RxJS.
> It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
Don't sully UNIX philosophy by comparing it to Redux! UNIX is such a different environment from JS space that it's hard to really even compare the two, but at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
> The observer pattern is mentioned in the Gang of Four Book[1], published in 1994. JavaScript first appeared in 1995. Observers predate Redux. You don't need Redux to use observers, and in fact there's nothing about React that is incompatible with RxJS.
Yes, and before I was using React+Redux+Redux-Observable I was using Cycle.JS and everything was RxJS and/or Xstream (an RxJS-like) observable-based patterns.
As I said, this stack is a useful "oreo" for my needs right now. I understand if "oreo" isn't your cookie of choice.
> at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
Again, I think the problem here is that we are disagreeing on what the problems even are. Again, I don't think these tools "create problems", I believe they solve very specific problems, and yes very "unix" in that way of solving as specific a problem as they can and no larger, and leave other problems that already existed untouched, whether or not they were in play, because again that's the philosophy here. React is "just" a view layer with a bare modicum of state responsibility. Redux is "just" a state layer. Redux-observable is "just" a tool for handling state side-effects. They don't need to solve every development problem, this isn't Angular. Similarly, they aren't the only tools for the job. There are several alternatives to each part of that stack (as others in this thread keep pointing out), this is just the one I've chosen for my projects right now.
The obvious and most common is asynchronous state that has some associated loading process/time. Especially those situations where loading some top level object implies to start loading a collection of lower level objects.
Reacting to object changes (persisting them to a database, or otherwise audit logging them).
Kicking off and managing "background tasks" like chron-job equivalents (5 minutes after a state update do some other related thing, such as a automatic state machine transitions), database synchronization/replication processes, cross-checking/merging in data from GPS/geolocation/compass streams, etc.
Have you tried MobX? If yes - could you share your criticism?
I liked it more than redux, because you can have have simple class implementation and side effects without much hassle. Also TS support and testing is easy.
I have not tried MobX. It smells a bit too much like Aurelia, and I've been burnt a bit by Aurelia. More generally, too, things that make heavy use of ES2015 Proxies for side effects tend to make me pause, simply by nature of black/gray boxing those side effects, and also making it a little harder to control/throttle them in cases where performance matters more than "magic".
(Which is an interesting turn of events, personally, because I really liked Knockout ages ago, but its attempted successors in MobX, Aurelia, and even Vue leave me cold. I think partly because Knockout was so much more verbose and forced/needed a clearer "MVVM" separation simply by how "dumb" it was, whereas Proxies and Descriptors gives too much of an illusion that you are working with "plain" objects that don't need a clear "MVVM" separation until it is sometimes too late to fix a maintainability/tech debt hole.)
The problem that you end up with past a certain scale in a component-based app without a state management framework (third party or your own) is that you're performing business logic inside view components. By moving your business logic into your state management framework and using something like redux-saga that is designed specifically for managing complex asynchronous workflows, you can extract your application code from your views and make your views pure functions of the state tree.
When I hear someone complaining about redux using global state, I like to ask them what makes global state bad. The answer is usually that it allows any part of the application to change a value, which makes debugging difficult. This doesn't apply to redux and similar frameworks because every slice of the state can only be managed by a single, pure function, which makes it incredibly easy to debug. Additionally, redux uses a copy on write mechanism, so your state tree isn't actually being mutated, which allows you to use equality comparison instead of a deep compare to see what state has changed. That, combined with the fact that all actions go through a central dispatcher that is easily audited means unit testing your state transitions is almost laughably trivial. And since you are pulling all of your business logic out of your UI components, you can avoid the side-effect hell of unit testing your UI.
Having said that, I won't use react or redux for any website that doesn't have a heavily interactive UI with complex state that must be shared across many pages. Managing application state in your front end is an enormous burden and you'd be crazy to take it on without a need for it.
> The problem that you end up with past a certain scale in a component-based app without a state management framework (third party or your own) is that you're performing business logic inside view components.
I respectfully disagree here. All UI views in React can be controlled via parameters. Very few components in a React application require state management. You can add parameter functions to handle various events as well.
> which allows you to use equality comparison instead of a deep compare to see what state has changed
You are doing a lot of deep compares on state in a real world application? Why? If you only use parameters for the state of your component, everything is already handled by React out of the box.
> every slice of the state can only be managed by a single, pure function
Really? You can trigger reducers from literally anywhere in your application. Not only that, you can create multiple reducers to handle each action type. I've seen a situation with 24(yup, 24) reducer functions for a single action type. Many of those reducers contained complex business logic checking the overall state of the application. Then you have 2 or 3 action functions all with different business logic for that one type. Race conditions everywhere. setTimeout 0, here we go again. Step debugger hell. Of course, you should never do this, but in the real world, this is what happens over and over again.
> means unit testing your state transitions is almost laughably trivial
If you use parameters for state of a component it is 1,000 times easier. Because a component's view is not coupled to the global state of your application. Pass it parameters, test that it looks correct. Change a parameter, check if it is correct. Dead simple. Your tests are not bound to global state at all. Again, you started with an anti-pattern of managing state in child components, and now, instead of not doing the anti-pattern, you are adding yet another tool to fix it. Instead of just not doing it.
In summary, what you are describing is the "theory" of why Redux and associated tools should work. In every case that I've seen(10+ at this point) that has not happened. Not because Redux is a bad tool. Its not. But in the real world, it has become incredibly expensive for many organizations to maintain.
> Very few components in a React application require state management.
I agree. My apps usually consist of a few stateful components for dropdowns and modals and such with the remaining being functional components. The problem is the majority of "legacy" react apps I've come across seem to have the opposite ratio (I personally think this is because most react tutorials are written for fresh out of bootcamp developers by slightly more experienced fresh out of bootcamp developers).
> You are doing a lot of deep compares on state in a real world application?
Not a lot, but when you need it you really need it (usually for shouldComponentUpdate, but most recently I needed it for synchronizing state to localStorage).
> I've seen a situation with 24(yup, 24) reducer functions for a single action type.
You're right, that is insane. I personally use thunks or sagas to dispatch multiple actions that are specific to the domain model being updated and never update multiple slices of the state tree with a single action. For example, the action dispatched by clicking the sign in button should not update the user profile and the navigation state. Again, the problem in your example is not redux, it's a lack of separation of concerns.
> Become a components view is not coupled to the global state of your application.
The state of your application has to go somewhere, and if you are creating a small amount of stateful components and a lot of functional components, you are doing a lot of prop drilling, and every time the stateful component is updated the entire component tree gets re-rendered. If you keep the stateful components at the minimum height necessary, your business logic will be split up according to where it needs to be used instead of logically grouped by feature.
> summary, what you are describing is the "theory" of why Redux and associated tools should work.
You are arguing that redux is a poor choice because bad developers write bad code. I'm saying that the constraints that redux imposes on the structure of your code makes it easier to enforce separation of concerns as the app scales in complexity. The things about redux that most people complain about are the things that make it work so well in complex applications. I have seen unmaintainable apps that used redux, and equally unmaintainable apps that used component state. I don't think anyone can accurately say that one is worse than the other in a pathological case. At this point we're just arguing about opinions, but I maintain that explicit structure using shared conventions is still better than ad-hoc structure that requires institutional knowledge to navigate, even in the hands of untrained code monkeys.
One thing I have noticed is that every time react or redux comes up on HN it starts a flame war with the same arguments rehashed over and over, and when I get involved in these discussions none of my comments ever get any up or downvotes, which tells me that nobody is reading this and we should get back to work. :)
Great points all around. One question though — how many apps actually need to share state across pages? Seems like mobile apps have this problem more than desktop apps, because their screens are small and so the UI must be split into pages. Whereas a large monitor can support an expansive UI that isn’t “split up”. So if you’re making a desktop app, what would you use instead of redux? Seems like your points about splitting off business logic, debugging and testing all still apply when the state is confined to a single heavily interactive screen with complex state.
If you don't have any routing to deal with, component state is all you need. You can still move your business logic out of the view components and use lifecycle methods and event handlers to coordinate it, but the use case you are describing is the ideal case for vanilla react.
If making global state accessible is all you need Redux for, then I don't see why that state shouldn't be:
1) persisted to a back-end and re-fetched on route
2) Persisted to localStorage
If there some kind of state that doesn't neatly fit into these solutions? I know that passing around signed-in user objects is one popular use case for Redux, but in my opinion that could be easily placed into localStorage, or assigned to a window.global_state variable.
This is a huge pain in the ass and requires a ton of really tricky UX design. What happens when the data is cached and you fetch it again? Do you disable the UI and show a spinner until the request is complete (why bother caching the data in the first place)? Do you allow the user to continue making changes while the request is pending and try to reconcile conflicts when the payload is parsed (this problem is far beyond the skill level of most developers)? When the data is loaded, do you just change the content out from under the user or do you write a series of complex animated transitions to make it less jarring?
Updating state on every transition is hard, and keeping two copies of the data is also hard (cache invalidation!), which is why you shouldn't use an SPA framework unless you absolutely require it.
> If there some kind of state that doesn't neatly fit into these solutions?
The type of state is completely orthogonal to whether redux solves more problems than it creates. It should not be included by default on every project, and there are thousands of alternatives that might be a better fit. The heuristic for whether you should reach for something like redux is the size and complexity of your app, not the type of data you have. If your app consists of a single route without a lot of branches in your view tree and most of your components are only used once, you definitely don't need a state management library. Once you start adding multiple pages and lots of component reuse, something like redux starts to increase structure and maintainability at the cost of additional complexity. There is also a lot to be said for using an architecture that most of the react community is already familiar with so new developers can ramp up quickly.
> Persisted to localStorage
This is a standard practice, but you have to be careful not to spam localStorage with updates since it's significantly more expensive than in-memory object creation. I haven't profiled it but I wouldn't be surprised if it were several orders of magnitude slower. I sync the application state to localStorage in all of my apps, but I debounce it by 500ms and use equality comparison to prevent writes when no changes have been made. Obviously none of this has anything to do with redux, but redux's copy on write mechanism makes comparing states trivial, and any code that mutates the state trees requires an expensive and tricky deep comparison that negates the benefit of making the comparison in the first place (especially in the case of shouldComponentUpdate).
> assigned to a window.global_state variable
This is not the same as globally available redux state. For one, redux does all of the plumbing for subscribing to state changes and preventing unnecessary re-renders. You could spend a couple of days writing your own, buggier implementation, but why would you? Secondly, as I've argued elsewhere in the comments, the redux state tree doesn't have the aspects of global variables that make them such a dangerous anti-pattern. Writes are isolated to a single function, and all other references are read only, and it uses a synchronous message passing system with a central dispatcher that is trivial to audit. Another commenter complained about race conditions, but redux core is only designed to support synchronous operations, and there are well tested and widely used tools for managing complex, multi-step asynchronous state updates. If you have race conditions you are using the wrong tool for the job.
This is a bit off the topic of your original questions, but every time one of these redux conversations comes up there is so much vitriol from people who just absolutely hate redux for some reason, and almost every time every reason they give for why redux is a cancer is a well known anti-pattern. I guess they tried redux, hated the verbosity and saw botched implementations by other developers unfamiliar with redux, and decided that redux was the pr...
Thanks for the time and effort you've put into this thread, it has been very interesting reading your comments, which have a lot of content and a lot of experience behind them. I wanted to ask you about this because I've been reading about when to use Redux and when not to, and I had basically come to appreciate that it would be helpful for the kind of app that I'm working on daily. That app is very complex and very dynamic, but because it is a desktop app there isn't a lot of routing since I can fit a whole lot on one screen. When there is routing, it's usually to another view with enough complexity and code for a totally separate SPA.
So like I said, I've basically decided that Redux would be a great benefit for maintaining a consistent, easily debugged architecture for an app of this complexity, but then I came across several people talking about shared global state across routes as a good heuristic for knowing when Redux is appropriate. As mentioned, I don't really have that in my own app, so I was a bit confused by this and wanted to get some more details from you. I think you've cleared it up pretty well.
I'm completely in agreement with you about the likely source of dislike and complaining for Redux. Absolutely people jump to it too often when it isn't necessary. More generally speaking, I think there's a default attitude that front-end should be easy, and then people are rudely surprised when they find out that it's actually very hard. Distributed systems problems, cache invalidation problems, combinatorial scaling of complexity with every feature and source of user interaction... you already know this. They blame the team or the tool, but there's a fundamental complexity there that's unavoidable and deserves respect.
Anyway, thanks again, I've enjoyed reading your opinions on this.
> attitude that front-end should be easy, and then people are rudely surprised when they find out that it's actually very hard. Distributed systems problems, cache invalidation problems, combinatorial scaling of complexity with every feature and source of user interaction...
This sums it up so succinctly I'm going to borrow this next time I'm discussing react with one of my C# teammates. :)
If you want to chat more my email address is in my profile.
So far every way of doing UI I have seen isn't maintainable long term. Yet somehow things wind up being maintained even if it is in that "why me?" kind of way.
I think this says less about frameworks/libraries themselves and more about the amount and sources of entropy present in the front-end. That's really what we're dealing with here.
Libraries/tools/patterns/frameworks all usually have the function of containing some of that entropy. In theory the best tool contains the most entropy while allowing you to produce results you can sell to all stakeholders.
I think over the years we are doing better and better, but it's a long game. We're not all the way there yet, is it even possible to get all the way to the dream?
1. You're conflating a bunch of terms here, but I think you're referring to global application state changes being treated as a pure function which takes in the global state and event parameters, and returns a new global state. There's nothing stopping you from doing that without Redux; the state = function(state, event) pattern is supported by native JavaScript. If that's not what you're talking about, please clarify.
2. This is only marginally more powerful than a debugger.
3. I would consider this allowing you to shoot yourself in the foot. Explicit is better than implicit, and explicitly defining your dependency tree is a benefit, not a downside. When you're feel the small pain of having to explicitly define a data dependency, that prevents you from introducing it if you don't have to, thereby avoiding the much larger pain of having an overly complex dependency graph. By adding Redux you're adding hours of debugging time on complex global dependencies to save yourself a few seconds worth of keystrokes.
As an addendum to #3, it's a single state that is passed down as a global/app state. If you break it up with the context api, it can get messy quickly.
As to other comments about logic in the View or State, it's bound to happen... the two aren't actually divisive concerns. Personally, I'm happier to use Redux than seeing prop drilling everywhere.
Thinking of Redux as a 'detached state tree' that the whole app can subscribe too might give a better picture of some problems that it is solving. In React, when you lift state up and up, you mostly end up tracking lot of irrelevant state in the top most component which kind of weirdly manages lot of state just for their children. There are some genuine use cases of Redux by tracking the whole 'app state' in a detached way, safely update/transform them (with pure functions) and more importantly you could just subscribe for necessary data at any level. Context API is helpful, yes, but Redux (or MObX or any state management tool) is still helpful when done right.
> In React, when you lift state up and up, you mostly end up tracking lot of irrelevant state in the top most component which kind of weirdly manages lot of state just for their children.
I'm not sure why you think the data is irrelevant, or why the component manages it "weirdly".
I'll point out that these top components actually share a lot in common with Redux, except that they can be duplicated and composed with other components, if for example you decide to drop your top component inside of a larger application, and the toplevel components actually don't end up managing data they don't have to (LESS "irrelevant" data than Redux, perhaps?).
> I'm not sure why you think the data is irrelevant
I believe the GP meant "unrelated". Can you drop your top level component into another application with different children and still have it work? Can you move the child components to another view tree and still have them work? It seems like what you are describing is a top level component that is tightly coupled to its descendants with too many responsibilities. In the ideal react app architecture you should be able to take any component and move it anywhere else in the app and it should Just Work. This is the problem redux is supposed to solve, and I would argue that by enforcing an explicit pattern that most experienced react developers know, you will get closer to that ideal than if you have a variety of home grown solutions that vary from project to project (and even from developer to developer on the same project). "The Tyranny of Structurelessness" etc etc.
> This is exactly not what happens in Redux projects in my experience.
Sorry to beat this dead horse, but how does redux, which binds data directly at the level of the component that displays the data, make portable components more difficult than vanilla react where stateful components and functional components are tightly coupled? Do you have links to a gist or something so I can see what you're describing?
I have to let you in on a big secret. State has always been global. Have you ever worked on an application backed by an RDBMS? Redux simply moves state. It doesn’t expose it any more than a database.
I've found that explaining Redux as an in-memory database/ORM helps a lot of colleagues get over the hump of what Redux is for and why people often feel a need for it.
361 comments
[ 4.1 ms ] story [ 80.5 ms ] threadIE. Purely JS driven, no browser-page loads after the initial page has loaded.
I had actually never seen that written solely as SPA even though I've worked as a react dev for 2 years now in Canada. I guess the acronym really is going obsolete.
Without JS, Server Side JS defaults to good old school MVC.
A lot of the "modern" toolsets just aren't worth the extra fluff they add.
Writing SPA's in React, Angular, etc. tend to result in a very heavy first page load, then api calls with poor UX for all actions.
React is about components and you can use React in something like NextJS which is not a single page app but a server rendered pages.
Also, SPA itself is getting outdated concept, now most apps are hybrid of initial bundle followed by a dynamically imported modules. Finally, MVC is a pattern for web pages, the complexity in the client side because what is the being shipped to the browsers are a client side apps with their own router, render functions, state management and APIs.
Once a SPA is loaded you can send tiny crumbs of data faster than any significant SSR.
Because a user doesn't care if it's the initial frontload or not.
You can, but often that's not what's happening.
E.g. crates.io is currently built as an SPA. It is first receiving a HTML page that loads a javscript file, that performs an XHR request (actually, multiple) that answers with JSON that is used to create the final DOM of the page. The Javascript file is cached but that's about it, it still involves one additional needless roundtrip for the website.
The JSON sent is so badly designed that it alone is far more than the final DOM of the page.
Jus check out this: https://crates.io/crates/winapi
vs this: https://crates.rs/crates/winapi
Only the XHR requests for the crates.io page take up 99 KB uncompressed, while the entire crates.rs winapi page takes 103 KB uncompressed.
While SPA tooling like React are incomplete since they don't provide all of the client/server data communications that comes for free with a server side application. That is starting to become more "standard" as GraphQL and Apollo start filling the gap of boilerplate. Though of course wrapping your head around GraphQL takes a bit of work for anybody who's spent their life with REST.
Most applications should be SPAs to create the interactions that users expect.
Which is ironic to me. As soon as the teams I've seen start piling a ton of extra stuff into the UI, be it types to make things more safe or logic to make things feel faster, you get a mess.
Note I'm not claiming either of those are the problem. More code is just almost always more mess. Keep it small. If possible, keep it separate.
1) You have the problems that different developers have different styles. So when you jump from one area to another in the code base you're hit with WFT this is functional/procedural/inheritiance/composition, etc. style and your first thought is "barf" they did it wrong...
2) People either have huge functions that are spaghetti or tiny little functions to avoid complexity that are one line if statements.
Both of these are usually rooted in lack of a shared code culture, these are not SPA issues but team issues.
There are some languages that are so “powerful”, “flexible”, and unopinionated that every developer has dramatically different coding styles (JavaScript, Scala, etc.). On the other end of the spectrum are tightly prescriptive, constrained languages where everyone’s code looks the same (Go comes to mind).
No doubt team culture plays a part as well, but in team settings it is important to pick tools that are designed for team settings. These tend to prioritize regularity, lack of flexibility, syntactic simplicity, static analysis, etc.
I know the world is sick of gardening metaphors, but I do feel like there are lessons from community gardens. (Really any community thing.) You will usually have an owner that will paint broad strokes of what the rules are. Then, a bunch of spots that contributors are touching regularly. In their contributions, the rules are much more free form. But, you don't typically have people moving rapidly between contribution zones.
Which gets me to my favorite take on the answer. Solving the customer problem is not necessarily the same as solving any developer problem. If you are lucky, you can align them. However, as a developer brutal honest with self in "did this change actually provide any value to the customer" has to be constantly asked.
[1] https://github.com/turbolinks/turbolinks
https://github.com/turbolinks/turbolinks/blob/master/README....
Turbolink is a crutch for people that are not able to server side render an html in a few milliseconds.
It's fetching and replacing the HTML that needs to be changed, but removes the need to do a full-page reload, so the browser doesn't have to re-load and re-parse all of the HTML, and JS, and CSS. Even though the browser will keep a lot of this stuff cached, Turbolinks can make a site feel a lot faster from a user's perspective. But regardless, it's more than just a loading bar at the top of the screen.
Whether it's the right fit or not depends a lot on what you're trying to do.
so if i have to rewrite my js with this just so i could fake SPA then benefits are so much smaller
https://stimulusjs.org/handbook/origin
Compare this with rewriting your app as an SPA.
Most apps are fine serving html, using plain old boring forms, and having the odd React or Vue component where necessary to do some heavy lifting for user interactions. Moreover, it's a lot quicker as a small team to iterate.
Not that react isn't great for traditional websites, I've just seen too many fullstack or backend devs struggle with it, so if they're going to be involved in the frontend it may be worth considering the alternatives.
The updates are handled by JavaScript and http calls.
You can write a lot of that stuff in a dozen lines of JQuery with nicely refreshing content and ajax instead of throwing in large amounts of framework bloat.
When you have 2mb worth of "React code" and the framework itself only takes up 35.6kb, that's a lot of bloat.
And if the project isn't worrying about bundle size at all, then even their vanilla/jquery page is going to end up bloating a ton as well.
More often than not the culprit in 2mb bundle sizes is a a few packages that include "data" in the bundle (For example, i've seen timezone and locale information bloat bundles by megabytes, and in one case a 5mb bundle ended up being 4.6mb of zip codes...)
The PoC ended up working out, and we built the infra to correctly handle the lookups without needing to bundle the entire country from there, but man did that raise some eyebrows from a few other devs that noticed it!
Even ignoring that, React app sizes are getting better. Modern React apps (basically since 16.7) that are written with things like Suspense, lazy, and hooks are usually pretty small. Writing functional components and composing your app pushes you to write less code. Plus React gets things like code splitting for free with webpack if you use lazy, so the first page load only downloads things that are actually needed to get to interactive.
No doubt some less considerate developers will still manage to write giant apps that take ages to get started, but they don't have to. Bloated apps are a function of developer's choices rather than React (or any other JS framework) forcing the apps to be bloated.
Come on, 16.7 was released in December and 16.8, with stable Hooks, was last week! It's interesting that React has added these features and it bodes well for the future but you can't claim capabilities that have only existed for the a few months is what constitutes "modern." React's pace of adoption has been remarkable but that also means there's a lot of code that already exists that isn't going to be changed right away to take advantage of these features.
So like, a month ago?
If it isn't less than a month old, it's "the old way" :)
OTOH jquery is much bigger than you'd expect, there isn't much of a difference between a minimized jquery and vue, or even react once you factored in gzip.
And while I've no experience doing so using vue, writing small dynamic react components embedded in larger static HTML pages is quite enjoyable.
1: https://blog.codinghorror.com/the-magpie-developer/
Previously I had never built a real product in Rails, only the book store tutorial. I'm really happy with Rails so far. I don't have to worry about a lot of shit I had to worry about in JS land and now I can focus on the app itself.
If you start with how it's going to end -- it's not going to end well.
Having said that, what you're most likely seeing is that a ton of tools and frameworks are built for no other reason than 1) Coders need something to create to show other coders how awesome they are, and 2) People want something that works like everything they know -- it just does these one or two new things
This is a great way to make a mess over a decade or two, and you're right for waiting it out.
Make a SPA if you need a SPA. Key question: How do I know whether I need a SPA or not? (Or whether React makes sense, etc)
- Render all pages/routes/states into a single html file (size ~2mb) but each page/component/state hidden via style=display:none;.
- then write some hand crafted js (few hundred lines) to add event listeners to forms and show/hide the components depending on url change
- cache everything via appcache/serviceworker
- result: not so fast initial load (1-2 seconds) but really fast interaction afterwards even on old lowend devices, almost no time spent running js/react/vdom. even some scrolbugs I had to work round before when using react (eg replacing dom nodes but keeping scroll position of a parent container) resolved on their own.
That's only 1-2 seconds on a fast connection.
If you have zero of that content cached locally and are on a mobile or just not-fast connection it'll be way more than 1-2 seconds.
Your "solution" is one of the ones responsible for locking up mobile browsers all over the place, because the browser still has to deal with the entire DOM, and it's often done way inefficiently.
nothing else because all images are already inlined as svg as well as all styles and the mentioned js code
> Your "solution" is one of the ones responsible for locking up mobile browsers all over the place, because the browser still has to deal with the entire DOM, and it's often done way inefficiently.
I only came up with doing it this way after previously having build the same app using react and old devices (eg android pre chrome) could not handle all the vdom diffing and add/removeNode.
Our users preferred a slightly longer initial load (even 10 seconds) and lightning fast interactions afterwards over fast initial load and sluggish UI for all the time using the app.
When doing client sit rendering you would still have to transfer and parse little chunks of json all the time.
As it turns out browsers are really good at handling even huges DOMs as long as you do not manipulate it to much.
Granted this way only works if you have a limited set of states/pages (eg a few thousand).
Do you have a public implementation available for review? I'm having trouble understanding why this would be the case (I am pretty familiar with React's implementation).
>Render all pages/routes/states into a single html file (size ~2mb)
That's not good at all for mobile web pages.
[0] https://app.ishl.eu/
Then I tried mobx, I was able to write a small app after 15 minutes of reading the docs and now I love react.
Not that either one was really all that necessary in the first place anyway.
I mostly use setState nowadays and it's enough.
Meanwhile, relying on setState alone for complex components ends up being very error prone and tedious. I still rarely use MobX for global state because I prefer the original appeal of React in terms of making modular, reusable components, but I often use MobX as the state mechanism for my more complex components and bypass having to rely on setState altogether for those components.
I'm currently maintaining alone two large websites and only one of them is an SPA, that's definitely the harder one to maintain by far.
React with well defined static content routing and server-side rendering is amazing at this because the first packets that the server sends the user is html/css jumble.
You can get your app to show text and formatting in as little as 20ms(depending on where you deploy it), then show images, then you don't care if the rest of App and all the js and 3rd party crap take another 2.5s and 3mb to load, The user will do a bit of reading and picture watching, before using a button or input field. However if they're on the go and loose connection, now they have a fully functional back button in their browser to read the next or previous cached html/css content.
We've been "server side rendering" since the inception of the internet.
If you build your pages with CSS at the top and JS at the bottom you're already achieving most of that effect right there, no extra tools needed.
The browser will load the css and content first, and then process the JS requests (Which are easily cachable by the server AND the browser).
There's nothing new or especially special about "server side rendering", and nothing about React rendering either.
Rendering on the server lets you do whatever you want.
Want to put the user in a specific state? Sure, go for it.
Want to use a starter or null-state? Sure, go for it.
I like separation. I know that one side ( ruby, rails, gems, pg, sql etc ) is all about data -> one mindset.
Another side is about views ( js, css, html, multiple select, date pickers, autocompletes etc ) -> different world.
I build my assets in one place, I work with my data in other. I wear one hat as a backend guy, and when I need refocus completely on front end work.
I wouldn't argue it's easier, but I think it's easier for me. I like it better.
Plus, the API will be consumed eventually by user-facing site and apps.
For that latter part, although I must recognize that the tooling has evolved into a quite complex pipeline (which is a cognitive investment, almost a gamble, in itself), I also clearly see the benefits of a set of ideas that have rapidly evolved in the turbocharged settings of the web development ecosystem:
React: the distillation of many programming paradigms (functional, composition over inheritance, etc) for building UI components.
TypeScript: the augmentation of JS with mature and powerful programming constructs.
Material: a visual language for UIs with a strong focus on coherence and being truly cross-platform.
All in all, although I'm well aware of the critiques that can be addressed to the modern webdev environment (the so-called "JS fatigue"), I feel like all these new tools empower me, and I really enjoy learning about them.
Lol, a 3 year solution. I immediately feel sorry for the future devs jumping into a codebase like that on New Year's 2023 trying to figure out what broke overnight.
https://dockyard.com/blog/2018/12/12/phoenix-liveview-intera...
I hope to see this idea brought to Rails, but the its websocket story doesn't seem there yet.
HTML rendering with Phoenix is lightning fast. https://www.bignerdranch.com/blog/elixir-and-io-lists-part-2...
The modern, offline web is great. If we had to do this in a native app we would never have gotten the project off the ground, because we initially need to use BYOD phones which were evenly split from iOS/Android. We'd have had to use three languages, but a Mac, get developer licenses, and learn Swift and Java just to get an offline frontend, and sharing business logic code would be impossible.
http://guide.couchdb.org/draft/conflicts.html
I don't want to go back to Rails/PHP/Python though. I like having a REST/GraphQL API. With Hasura I don't have to worry about backend anymore for 80% of the cases.
What I'm doing right now for the front end is using Jekyll with Vue. It's really liberating not having to worry about a router or managing application state anymore. For certain in-house projects I don't even have to use Babel or Webpack anymore and still get to use Async/Await.
These, combined with Angular CLI or Create React App, allow me to be just as productive as the pre-spa days
Nobody worth their salt would recommend coupling display components and database manipulation together in an iOS or Android app. A webpage is the same thing, because it's not running on your server, it's running in the browser on the user's computer.
With this blog post, like most "I don't like X technology" blog posts, I find myself asking, "does the author not like this technology, or has he not taken the time to become a real SME in it?"
We had a SPA using OAuth.
I spent a few weeks adding docs, ran Swagger codegen, then declared it finished. Every call our front-ends used was now part of an API. Then we just trimmed out the parts we didn't want to officially support.
a SPA is simply a SPA. Similary a carrot is most definitely not (!) an carrot. An apple, however, is an apple, and an uncle is an uncle.
Carry on...
People can, and have written bad websites using every technology. I can think of plenty of badly written, hard to use MVC websites from my past.
It should come as no surprise, that bad developers have found ways to make badly written SPAs as well today.
I don't think we should judge a technology by its worst implementations.
With that approach, you really wanted to keep every component self-contained. That meant a lot of bookkeeping with passing events back and forth between parent and child widgets, but while it was a lot of typing, it was all fairly easy, and if I wanted to change how an event was handled, it usually only involved a component and MAYBE the components immediately above or below it in the component tree. Data passed further than that was generally wrapped in objects and therefore didn't require code changes.
Then introduce Redux and similar frameworks, and I totally lost interest. It's as if the entire React community forgot all the lessons they learned writing Python and C programs in college and went back to creating global state. What in the actual hell? I wrote a good amount of code in Redux, too--I worked on teams that used it--so this wasn't just pre-judgment. These frameworks add a huge amount of hidden global complexity just to avoid having to pass parameters to components, which is the simplest thing to do.
And once you're doing that, you HAVE TO write an SPA, because any part of the page you want to be interactive gets pulled into Redux and your JS eventually eats the whole page. It's an infection.
When writing JavaScript now, I sometimes end up rolling my own version of the component style. It's an effective design pattern for UI widgets. But it hasn't been worth it to use React for a while now because the tooling and tutorials are all too conflated with Redux and similarly awful tools. And on a team, there's always the worry that someone on your team will infect your code with Redux and it will grow and consume your entire site. It's a real shame.
Many people just use setState.
Since my last Redux project in 2015, I did many React and React-Native projects and not one of them used Redux.
I only did green-field projects where I was the only front-end dev.
If you don't use redux, then keeping state synced with a server side rendered application because nearly impossible.
Could you explain what you mean by "server side rendering state"?
The client receives this initial state and uses it with the html to glue together a working UI on the client.
This removes the massive JS bloat into raw html and split JS for each component.
Instead of sending 3MB of JS, you send only the JS needed to render the current page... and send additional JS as a user navigates through your website.
Here's an example with Create React App: https://github.com/cereallarceny/cra-ssr
Gonna have to quantify that. Cause I imagine if you did, you'd redact that statement. Redux is a very straightforwards and SMALL library that handles global state.
Its concept can be difficult to follow at first which may cause some to think it way more complex under the hood than it really is.
Redux is small.
> the latency cost of loading small parts as you go
The components are static JS split into files.
Pop those into a CDN and you get the best of both worlds.
Fair point. But could it be possible that there are lots of people doing React that never went to college and never wrote Python or C programs?
Sure, global variables make debugging difficult when you don't know which function changed them where and when and why. But that doesn't happen if you keep your functions pure and only make changes from within reducers – at least that's my experience.
(Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
And I thank my teacher for knocking the bad habit out of me. So when I see global variables beyond the initialization of core objects, I take it as a bad code smell.
Could you explain what you mean by this?
My intuition here is that what you're saying is true in a trivial sense, but that most state CAN BE (and should be) local if you're breaking your programs into reasonable building blocks.
All that information exists only once and is needed in many parts.
Also, "state" does not necessarily imply "mutable". (Redux encourages an immutable state approach where good reducers don't mutate state in place, but instead build new state from old, though Redux is not strict in enforcing this best practice. For Redux the "current state" is "file handle" that simply updates from one object to the next, not all that different from stdin/stdout/stderr or even somewhat to static configuration that is somewhat updateable without restarting the application.)
> Also, "state" does not necessarily imply "mutable".
Okay, sure. I'd rather not get into a semantic argument about the meaning of the word "state". Suffice it to say that I'm talking about mutable global state.
I think many people could argue that this concept itself is a bit misguided. There are plenty of application patterns and architectures that help you move stateful data around your app and translate it for the components who need to consume it or change it, while hiding it from components in the tree which do not interact with or care about the state.
If you have many different components in your app that all need access to the same state data, and they exist at different levels in the hierarchy, maybe you need to re-think your component structure rather than figure out a way to inject mutable global state everywhere?
First question (show user data in nav bar, user details page, and "comment thread"):
User profile page is obviously showing full user details, so you're going to want to have a separate endpoint for "getUserDetailsByUserId(int userId)" or however you want to name it.
What data are you showing in your nav bar? Is this just a user-avatar image link/button? You would rarely want to display anything other than an avatar and/or username in a navigation bar. You could maybe talk about a navigation drawer instead, where you have more simple details like maybe email address and phone number or something.
What about the "comment thread"? Are you showing anything more than an avatar and/or username? This sounds like the same use-case as the nav bar... how would the display differ here?
If you have use-cases for anything more than your basic "user avatar" and "full user profile," (and maaaybe "user basic info," for a "nav drawer" or something, although those are being phased out of most current design standards) then I would seriously urge you to re-think your app design. That polish of showing the user info in a slightly different slice on some specific page is almost never going to pay off for your company, imo.
Anyways, if you want to be naive about all of this, you just save the userId (and token or whatever, but that should be in your http middleware) after a new login session, and then you request separate "getUserNavBarInfo(userId)", "getUserDetails(userId)", and "getUserCommentInfo(userId)" and load the results of those into your UI views on-demand. If you need to progressively tune/cache your API, you do this separately based on usage stats for each of these 3 use-cases, you don't start with directly caching "Users" and adding the business logic client-side to translate into the 3 use-cases.
If you want to do a more thorough dependency-injection "user component," then this might vary depending on your DI framework, but the high-level idea would always just be to create a user module with all the info needed for a given user, wire it up to reload the module in your DI graph on new login and on-success of any request to update user info. You could have both your "login" and "update user" endpoints both return the full set of data needed for the "user module" on success. Then you need to set up your DI injection component scoping so that your user info injection component is dumped on user logout and settings change, and is recreated to do it's injections with the new/updated user module each time. A singleton DI "user component" that you must choose to inject into any UI component that wants to show user information seems much more constrained in complexity to me than just having the ability to arbitrarily pull out various bits of user info from a global redux state, at any place in your app.
Second question (single source of truth, increases or decreases complexity?):
I think this question is orthogonal to the capabilities Redux (and similar) provides. Redux has nothing to do with outright increases or decreases in app complexity, it is simply a tool that enables various (opinionated) architecture patterns. If you have a separate, domain-specific, well-defined event+data architecture that you are sticking to very strictly to keep a handle on complexity, then Redux can (imo) reduce boilerplate and allow very elegant interactions between various data/biz logic/UI components of your application, all without any "tight coupling". It's really just a message-bus pattern at some level. This sort of thing has been around longer than the concept of software itself, there are hardware components literally called "buses" that basically do the exact same thing (ship e...
I think these debates about global variables or state could be much more specific, though.
For example, we tend to realize that letting any component access a `require('./store').currentUser` singleton may make testing harder, but this global dependency still exists when, say, it's distributed to all components through the React context. I reckon most people are thinking of the singleton example when they lambast Redux but it certainly doesn't have to be that way.
I'm just not buying this argument. People add Redux to projects because they intend to use it, and there's almost never a case where a piece of data is actually needed in enough places that it should be global. It's certainly not common enough to justify importing a whole framework for managing global state.
Sticking to reducers IS a good idea and it does make things a bit better. The problem with global state goes deeper than that, though.
Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
With independent components, you do spend some time synchronizing data across components, but each component has the option of how to represent its data internally, so the effects of changing data representation in one area are usually very local, and each area of the code gets to represent the data in a way that's reasonable for what it's doing.
I'll also add that with independent components, you sometimes get components whose only real job is to manage data. That's actually sort of like a Redux! But you can spin up multiples of them without having to manage an array, and compose them or even nest them without issues.
> (Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
To be clear, I don't really care about boilerplate. It's easy to generate that stuff, but even if you write it by hand it's faster than debugging implicit, hidden complexity. Boilerplate ISN'T the problem with Redux, as far as I'm concerned.
> Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
or this:
> With independent components, you do spend some time synchronizing data across components
I'm not even hesitating before I choose the first option. My editor, static analysis, and the mystical art of the function can give me all sorts of help with the first problem. Good luck with those sync bugs.
Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way. In the most complicated situations, it actually exacerbates the issue because it separates the data from the components that use it, making static analysis tools unable to see how your data changes might break the components. This also goes to show you didn't even understand the part of what I said that you selectively quoted, since I already said this.
> I'm not even hesitating before I choose the first option.
Perhaps if you hesitated a bit more, you could use that time to read all of the options and understand them.
> Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way.
It's kind of the whole Redux philosophy that you won't do that. It's in the FAQ. There's no "can't". You're never forced to keep duplicate data in your store.
Restrictions are the price of solving hard problems (even if you don't want to admit they're hard) in a deterministic fashion.
I would argue that synchronizing data between multiple representations is an anti-pattern, and the exact anti-pattern that redux, with its emphasis on a single source of truth, was designed to solve. Synchronizing state is almost always going to lead to hard to diagnose bugs in edge cases. There's even a blog post on why it's a bad idea on the official react blog [0]
My solution to this problem is to use computed values generated by a library with memoization like reselect. I structure my data as close as possible to the API data models and use selectors to transform the models into the data structures that disparate UI components require. If you need to change your UI component, you don't need to refactor your store, and if you need to change your store structure, you don't need to refactor your UI components.
[0]: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-d...
I'm not saying there aren't solutions to the problems I'm bringing up. I'm saying you don't have to solve them in the first place if you don't bring in Redux.
You keep saying that you won't have these problems if you don't use redux, but I am arguing that _you do have these problems_, you're just creating ad-hoc solutions for everything that you would use redux for, except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs. Note that I have already agreed that there is a scale where redux is overkill and creates more problems than it solves. Once your app has a few dozen components it's time to start thinking about another layer of abstraction.
And then you're right back to any component that uses User being tightly coupled to the structure of the User, without any of the syntactic cues that allow you and your static tooling to connect the two.
> except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs.
That's not the case:
1. There's nothing preventing you from treating components as a pure function of their props.
2. At the top level, it's necessary to have some mutable state, but I'm not sure where you get the idea that this isn't testable. Redux doesn't remove mutable state: all redux is doing is a big complicated version of newState = pureFunction(oldState) which is perfectly possible to do using Vanilla JS functions.
If you keep your state in a component, you don't need any housekeeping, once the component is gone, the state is gone.
This is not true with Redux. You have to keep remembering to clean the state after you're done with something or you're going to introduce a bunch of bugs.
Redux offers a few benefits:
1. Allows you to keep your global application state 100% predictable by treating it as a pure function of an event stream (dispatched actions). This makes it trivial to test.
2. Allows you to "time travel" the state of your store using the Redux dev tools by replaying/modifying the event stream. You can also set up QA folks to export their events so that developers can reproduce any bugs in the global state 100% reliably.
3. Allows you to access global state without explicitly passing down props from great-grandparent to great-grandchild.
While I'm not a fan of using Redux for everything (it does have costs), you mentioned only the most minor benefit. If you only want #3, you would just use React's built in context API.
https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
The issue with Redux is that it encourages side effects in components. After a while people are dispatching all over the place and you end up in a similar situation as you were with keeping track of JavaScript events. All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
If you can convince and enforce that all Redux activity only happens at the root component, you should be okay. But the reality is that once you have the discipline to only mutate state in your root component, then setState tends to be more than enough for anything but very large and complex interfaces.
With that said this article has the author implementing their homebrew bespoke event system which is likely even worse. This was a very popular and much maligned anti-pattern during Angular 1.x days. This article is suggesting the absolute worst possible idea for maintenance.
What kills the maintainability of React and similar frameworks is tracking down what component did what. Unfortunately, in real world examples, in my experience, Redux increases that issue and ultimately leads to a very difficult application to maintain long term.
The only maintainable projects I have worked on in terms of web apps have been with Redux.
Redux is not great for simple projects, it is great for medium and large scale projects.
> All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
React, redux, redux-saga -> this is the recipe for large scale applications that I have used, built, and helped maintain with success.
One of the strongest benefits of redux is being able to see every single event in a debugger that displays current state, action payload, next state. It really does make tracking down state changes a matter of reading a global log.
Furthermore, because redux is not dependent on react and under the hood it is simply and event emitter, it becomes relatively easy to use the same exact business logic for one web app and use it for some other delivery.
We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
I'd say that the only times I've seen Redux work effectively at all is when the project was small enough that the complexity remained manageable.
> > All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
> That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
I don't need a way to manage side-effects, if I don't use Redux...
I'll admit I haven't used Redux-Saga, but you'll excuse me being skeptical that introducing more complex tools will solve my complexity problem.
> We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
Again, this is not unique to Redux. It's perfectly possible to have your business logic completely separate from the UI layer without Redux. In fact, my experience is that Redux tends to cause devs to mix UI logic into business logic (and any sufficiently complex UI will have its own state and logic separate from the business logic).
I'm a consultant and in my experience this misconception is by far the most common source of problems in redux projects. Redux is a very small and simple thing so you have to build a framework around it. Even redux-thunk isn't enough - that's only 5 lines of code.
I wouldn't recommend react/redux for public facing sites if there's no experienced, pure frontend team. A more batteries-included kind of framework would be better. There's a lot of thinking to do and hardcore levels of scaffolding to maintain before writing anything useful and maintainable with redux. But it can absolutely lead to maintainable applications.
Also, I've never seen any homegrown "vanilla" frameworks or data stores work well as complexity increases. It usually ends up as spaghetti, especially with a growing team + growing business demands. Or best case a poor re-invention of the wheel. There must be brilliant counter examples but they're probably rare, I may never have the pleasure of working with one.
FWIW, we've got a new Redux Starter kit package that tries to be a bit more "batteries included". It includes utilities that help simplify several common use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state automatically. It also includes `redux-thunk` built in, as well as some middleware that check for things like mutations.
Please try it out and let us know what you think!
https://redux-starter-kit.js.org
I understand why folks have opted to create higher-level frameworks around Redux like Rematch, and it's not "wrong" that they've done so. But, looking at Rematch code, you can't really tell that it's actually Redux underneath.
With RSK, I've aimed for an intermediate level - decreasing the amount of things you have to write by hand and the literal number of keystrokes you have to type, but preserving the idea of "dispatching actions" and using reducers.
Glad to hear that you think it looks good! If you've got any additional feedback or suggestions, please let me know.
Which is why a lot of people that encourage Redux tend to also encourage one or more of the "side effect" managers such as thunks, sagas, or observables (my preference).
From my perspective, React is the thing with the problem: it doesn't have great state management for complex tasks / multi-component coordination. What I want to use is something like RxJS to manage such interactions and state, because it gives me a lot of power/control in a relatively easy way. One of the best options out there that I've seen is redux-observable, and thus Redux itself is just the "stuff inside the oreo" gluing React to all my interesting "epics" describing high level functionality in the applications.
It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
The observer pattern is mentioned in the Gang of Four Book[1], published in 1994. JavaScript first appeared in 1995. Observers predate Redux. You don't need Redux to use observers, and in fact there's nothing about React that is incompatible with RxJS.
> It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
Don't sully UNIX philosophy by comparing it to Redux! UNIX is such a different environment from JS space that it's hard to really even compare the two, but at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
[1] https://www.amazon.com/Design-Patterns-Object-Oriented-Addis...
Yes, and before I was using React+Redux+Redux-Observable I was using Cycle.JS and everything was RxJS and/or Xstream (an RxJS-like) observable-based patterns.
As I said, this stack is a useful "oreo" for my needs right now. I understand if "oreo" isn't your cookie of choice.
> at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
Again, I think the problem here is that we are disagreeing on what the problems even are. Again, I don't think these tools "create problems", I believe they solve very specific problems, and yes very "unix" in that way of solving as specific a problem as they can and no larger, and leave other problems that already existed untouched, whether or not they were in play, because again that's the philosophy here. React is "just" a view layer with a bare modicum of state responsibility. Redux is "just" a state layer. Redux-observable is "just" a tool for handling state side-effects. They don't need to solve every development problem, this isn't Angular. Similarly, they aren't the only tools for the job. There are several alternatives to each part of that stack (as others in this thread keep pointing out), this is just the one I've chosen for my projects right now.
Reacting to object changes (persisting them to a database, or otherwise audit logging them).
Kicking off and managing "background tasks" like chron-job equivalents (5 minutes after a state update do some other related thing, such as a automatic state machine transitions), database synchronization/replication processes, cross-checking/merging in data from GPS/geolocation/compass streams, etc.
I liked it more than redux, because you can have have simple class implementation and side effects without much hassle. Also TS support and testing is easy.
(Which is an interesting turn of events, personally, because I really liked Knockout ages ago, but its attempted successors in MobX, Aurelia, and even Vue leave me cold. I think partly because Knockout was so much more verbose and forced/needed a clearer "MVVM" separation simply by how "dumb" it was, whereas Proxies and Descriptors gives too much of an illusion that you are working with "plain" objects that don't need a clear "MVVM" separation until it is sometimes too late to fix a maintainability/tech debt hole.)
Sure, you don't need Redux in the middle of the "oreo", but it's also not the "wrong" answer, either.
When I hear someone complaining about redux using global state, I like to ask them what makes global state bad. The answer is usually that it allows any part of the application to change a value, which makes debugging difficult. This doesn't apply to redux and similar frameworks because every slice of the state can only be managed by a single, pure function, which makes it incredibly easy to debug. Additionally, redux uses a copy on write mechanism, so your state tree isn't actually being mutated, which allows you to use equality comparison instead of a deep compare to see what state has changed. That, combined with the fact that all actions go through a central dispatcher that is easily audited means unit testing your state transitions is almost laughably trivial. And since you are pulling all of your business logic out of your UI components, you can avoid the side-effect hell of unit testing your UI.
Having said that, I won't use react or redux for any website that doesn't have a heavily interactive UI with complex state that must be shared across many pages. Managing application state in your front end is an enormous burden and you'd be crazy to take it on without a need for it.
I respectfully disagree here. All UI views in React can be controlled via parameters. Very few components in a React application require state management. You can add parameter functions to handle various events as well.
> which allows you to use equality comparison instead of a deep compare to see what state has changed
You are doing a lot of deep compares on state in a real world application? Why? If you only use parameters for the state of your component, everything is already handled by React out of the box.
> every slice of the state can only be managed by a single, pure function
Really? You can trigger reducers from literally anywhere in your application. Not only that, you can create multiple reducers to handle each action type. I've seen a situation with 24(yup, 24) reducer functions for a single action type. Many of those reducers contained complex business logic checking the overall state of the application. Then you have 2 or 3 action functions all with different business logic for that one type. Race conditions everywhere. setTimeout 0, here we go again. Step debugger hell. Of course, you should never do this, but in the real world, this is what happens over and over again.
> means unit testing your state transitions is almost laughably trivial
If you use parameters for state of a component it is 1,000 times easier. Because a component's view is not coupled to the global state of your application. Pass it parameters, test that it looks correct. Change a parameter, check if it is correct. Dead simple. Your tests are not bound to global state at all. Again, you started with an anti-pattern of managing state in child components, and now, instead of not doing the anti-pattern, you are adding yet another tool to fix it. Instead of just not doing it.
In summary, what you are describing is the "theory" of why Redux and associated tools should work. In every case that I've seen(10+ at this point) that has not happened. Not because Redux is a bad tool. Its not. But in the real world, it has become incredibly expensive for many organizations to maintain.
I agree. My apps usually consist of a few stateful components for dropdowns and modals and such with the remaining being functional components. The problem is the majority of "legacy" react apps I've come across seem to have the opposite ratio (I personally think this is because most react tutorials are written for fresh out of bootcamp developers by slightly more experienced fresh out of bootcamp developers).
> You are doing a lot of deep compares on state in a real world application?
Not a lot, but when you need it you really need it (usually for shouldComponentUpdate, but most recently I needed it for synchronizing state to localStorage).
> I've seen a situation with 24(yup, 24) reducer functions for a single action type.
You're right, that is insane. I personally use thunks or sagas to dispatch multiple actions that are specific to the domain model being updated and never update multiple slices of the state tree with a single action. For example, the action dispatched by clicking the sign in button should not update the user profile and the navigation state. Again, the problem in your example is not redux, it's a lack of separation of concerns.
> Become a components view is not coupled to the global state of your application.
The state of your application has to go somewhere, and if you are creating a small amount of stateful components and a lot of functional components, you are doing a lot of prop drilling, and every time the stateful component is updated the entire component tree gets re-rendered. If you keep the stateful components at the minimum height necessary, your business logic will be split up according to where it needs to be used instead of logically grouped by feature.
> summary, what you are describing is the "theory" of why Redux and associated tools should work.
You are arguing that redux is a poor choice because bad developers write bad code. I'm saying that the constraints that redux imposes on the structure of your code makes it easier to enforce separation of concerns as the app scales in complexity. The things about redux that most people complain about are the things that make it work so well in complex applications. I have seen unmaintainable apps that used redux, and equally unmaintainable apps that used component state. I don't think anyone can accurately say that one is worse than the other in a pathological case. At this point we're just arguing about opinions, but I maintain that explicit structure using shared conventions is still better than ad-hoc structure that requires institutional knowledge to navigate, even in the hands of untrained code monkeys.
One thing I have noticed is that every time react or redux comes up on HN it starts a flame war with the same arguments rehashed over and over, and when I get involved in these discussions none of my comments ever get any up or downvotes, which tells me that nobody is reading this and we should get back to work. :)
If there some kind of state that doesn't neatly fit into these solutions? I know that passing around signed-in user objects is one popular use case for Redux, but in my opinion that could be easily placed into localStorage, or assigned to a window.global_state variable.
This is a huge pain in the ass and requires a ton of really tricky UX design. What happens when the data is cached and you fetch it again? Do you disable the UI and show a spinner until the request is complete (why bother caching the data in the first place)? Do you allow the user to continue making changes while the request is pending and try to reconcile conflicts when the payload is parsed (this problem is far beyond the skill level of most developers)? When the data is loaded, do you just change the content out from under the user or do you write a series of complex animated transitions to make it less jarring?
Updating state on every transition is hard, and keeping two copies of the data is also hard (cache invalidation!), which is why you shouldn't use an SPA framework unless you absolutely require it.
> If there some kind of state that doesn't neatly fit into these solutions?
The type of state is completely orthogonal to whether redux solves more problems than it creates. It should not be included by default on every project, and there are thousands of alternatives that might be a better fit. The heuristic for whether you should reach for something like redux is the size and complexity of your app, not the type of data you have. If your app consists of a single route without a lot of branches in your view tree and most of your components are only used once, you definitely don't need a state management library. Once you start adding multiple pages and lots of component reuse, something like redux starts to increase structure and maintainability at the cost of additional complexity. There is also a lot to be said for using an architecture that most of the react community is already familiar with so new developers can ramp up quickly.
> Persisted to localStorage
This is a standard practice, but you have to be careful not to spam localStorage with updates since it's significantly more expensive than in-memory object creation. I haven't profiled it but I wouldn't be surprised if it were several orders of magnitude slower. I sync the application state to localStorage in all of my apps, but I debounce it by 500ms and use equality comparison to prevent writes when no changes have been made. Obviously none of this has anything to do with redux, but redux's copy on write mechanism makes comparing states trivial, and any code that mutates the state trees requires an expensive and tricky deep comparison that negates the benefit of making the comparison in the first place (especially in the case of shouldComponentUpdate).
> assigned to a window.global_state variable
This is not the same as globally available redux state. For one, redux does all of the plumbing for subscribing to state changes and preventing unnecessary re-renders. You could spend a couple of days writing your own, buggier implementation, but why would you? Secondly, as I've argued elsewhere in the comments, the redux state tree doesn't have the aspects of global variables that make them such a dangerous anti-pattern. Writes are isolated to a single function, and all other references are read only, and it uses a synchronous message passing system with a central dispatcher that is trivial to audit. Another commenter complained about race conditions, but redux core is only designed to support synchronous operations, and there are well tested and widely used tools for managing complex, multi-step asynchronous state updates. If you have race conditions you are using the wrong tool for the job.
This is a bit off the topic of your original questions, but every time one of these redux conversations comes up there is so much vitriol from people who just absolutely hate redux for some reason, and almost every time every reason they give for why redux is a cancer is a well known anti-pattern. I guess they tried redux, hated the verbosity and saw botched implementations by other developers unfamiliar with redux, and decided that redux was the pr...
So like I said, I've basically decided that Redux would be a great benefit for maintaining a consistent, easily debugged architecture for an app of this complexity, but then I came across several people talking about shared global state across routes as a good heuristic for knowing when Redux is appropriate. As mentioned, I don't really have that in my own app, so I was a bit confused by this and wanted to get some more details from you. I think you've cleared it up pretty well.
I'm completely in agreement with you about the likely source of dislike and complaining for Redux. Absolutely people jump to it too often when it isn't necessary. More generally speaking, I think there's a default attitude that front-end should be easy, and then people are rudely surprised when they find out that it's actually very hard. Distributed systems problems, cache invalidation problems, combinatorial scaling of complexity with every feature and source of user interaction... you already know this. They blame the team or the tool, but there's a fundamental complexity there that's unavoidable and deserves respect.
Anyway, thanks again, I've enjoyed reading your opinions on this.
This sums it up so succinctly I'm going to borrow this next time I'm discussing react with one of my C# teammates. :)
If you want to chat more my email address is in my profile.
So far every way of doing UI I have seen isn't maintainable long term. Yet somehow things wind up being maintained even if it is in that "why me?" kind of way.
I think this says less about frameworks/libraries themselves and more about the amount and sources of entropy present in the front-end. That's really what we're dealing with here.
Libraries/tools/patterns/frameworks all usually have the function of containing some of that entropy. In theory the best tool contains the most entropy while allowing you to produce results you can sell to all stakeholders.
I think over the years we are doing better and better, but it's a long game. We're not all the way there yet, is it even possible to get all the way to the dream?
2. This is only marginally more powerful than a debugger.
3. I would consider this allowing you to shoot yourself in the foot. Explicit is better than implicit, and explicitly defining your dependency tree is a benefit, not a downside. When you're feel the small pain of having to explicitly define a data dependency, that prevents you from introducing it if you don't have to, thereby avoiding the much larger pain of having an overly complex dependency graph. By adding Redux you're adding hours of debugging time on complex global dependencies to save yourself a few seconds worth of keystrokes.
As to other comments about logic in the View or State, it's bound to happen... the two aren't actually divisive concerns. Personally, I'm happier to use Redux than seeing prop drilling everywhere.
I'm not sure why you think the data is irrelevant, or why the component manages it "weirdly".
I'll point out that these top components actually share a lot in common with Redux, except that they can be duplicated and composed with other components, if for example you decide to drop your top component inside of a larger application, and the toplevel components actually don't end up managing data they don't have to (LESS "irrelevant" data than Redux, perhaps?).
I believe the GP meant "unrelated". Can you drop your top level component into another application with different children and still have it work? Can you move the child components to another view tree and still have them work? It seems like what you are describing is a top level component that is tightly coupled to its descendants with too many responsibilities. In the ideal react app architecture you should be able to take any component and move it anywhere else in the app and it should Just Work. This is the problem redux is supposed to solve, and I would argue that by enforcing an explicit pattern that most experienced react developers know, you will get closer to that ideal than if you have a variety of home grown solutions that vary from project to project (and even from developer to developer on the same project). "The Tyranny of Structurelessness" etc etc.
Yes, it's a matter of passing the child into the parent as a prop.
> Can you move the child components to another view tree and still have them work?
Yes, this sort of thing is exactly what you get for free by doing things the way I describe.
> In the ideal react app architecture you should be able to take any component and move it anywhere else in the app and it should Just Work.
This is exactly not what happens in Redux projects in my experience.
Sorry to beat this dead horse, but how does redux, which binds data directly at the level of the component that displays the data, make portable components more difficult than vanilla react where stateful components and functional components are tightly coupled? Do you have links to a gist or something so I can see what you're describing?