309 comments

[ 1.8 ms ] story [ 291 ms ] thread
Mobx solved all of the state problems properly a long time ago.
Agreed! I'm surprised at the comprehensive work going into researching the history of state management in this article while completely missing MobX.

What problem does MobX not already solve?

All cited frameworks solve the problems. Article is more of a survey. It’s a pity to miss Mobx while discussing proxy/mutation API in Valtio.
came here to say the same thing.
What problem does MobX not already solve?

I’d say the two biggest hazards with the reactive/declarative style are cyclic dependencies in the data model and remembering history.

Tools like MobX let you write quite elegant code in the right circumstances but they are less helpful if, for example, you have a complicated set of constraints and the effects of changing one value in your state can propagate in different directions depending on what first changed to start the cascade.

This style also tends to emphasise observing the current state of the system, so if you need a history as well (for undo, syncing with other user actions via a server, etc.) then you probably have to write a whole extra layer on top, which is more work with this kind of architecture than for example if you are using a persistent data structure and reducer-style updates (as popularised by Redux within the React ecosystem).

This is a really great explanation - I love MobX in many ways but the “observing the current state of the system” definitely has its downsides, which you’ve expressed very clearly!
For a project that was basically an after effect like application I needed a history, but also very fast updates, settled with mobx-state-tree which gave me a way to preserve history. Worked great for that application, no regrets.

So there are some solutions available if normal mobx is not enough.

That's actually what converted me to Vue. I did all of the Redux stuff, but then found Mobx and enjoyed that so much more. A friend of my told me "if you like React + Mobx you'd probably like Vue" (I think he was quoting from the Vue website). He was right.
Shoutout to MobX-state-tree
It is a lot slower though, I had to migrate from it to normal mobx to come back to descent performance when having a few thousand objects.
Seconded, I looked at using MST a few years ago for a React Native app with frequent (30hz maybe) state updates and the CPU hit compared to plain MobX was too much
Mobx has made me finally appreciate frontend programming again. It's like people want to deal with the insanity that is redux only because it's a more pure functional style which react seems to promote.
Something I like to point out to teams when it's the case, and it's often the case, is "When is the last time anyone on the team has used time travel debugging? Never?".
Depends on what you mean here specifically :)

I'll agree that the Redux DevTools "skip action" and "jump back to action" features are not all that commonly used in practice. I _maintain_ Redux, and I don't even use them that often.

On the other hand, the ability to see a written list of all dispatched action type names is valuable by itself. So is the ability to click one of the listed actions and see the action contents, state diff, and final state. _That_ is very powerful.

Beyond that... I now work at a company called Replay ( https://replay.io ), and we're building a true "time traveling debugger" for JS. Our app is meant to help simplify debugging scenarios by making it easy to record, reproduce and investigate your code.

The basic idea of Replay: Use our special browser to make a recording of your app, load the recording in our debugger, and you can pause at any point in the recording. In fact, you can add print statements to any line of code, and it will show you what it would have printed every time that line of code ran!

From there, you can jump to any of those print statement hits, and do typical step debugging and inspection of variables. So, it's the best of both worlds - you can use print statements and step debugging, together, at any point in time in the recording.

Additionally, because Replay records the browser's OS calls, it captures _everything_ that happens in the page. That means you can debug _any_ website or JS app, no matter what framework it uses - React, Vue, Angular, Svelte, jQuery, or vanilla JS.

I actually recently implemented a POC version of support for the Redux DevTools in our Replay debugging app, so that if you do record a Redux app (or Jotai, or Zustand, or NgRx), you can use that same Redux DevTools UI to see the action history.

So, yes, time travel debugging _is_ an amazingly powerful concept. It's just ironic that that particular aspect of Redux didn't end up getting used that much... but the Redux DevTools themselves are still valuable, and Replay is actually a far superior "time travel debugger" overall.

Time travel debugging is fantastic and it's a shame Chakra was shelved and we have no great OSS alternatives in the works at the VM level.

What I meant though, was that people get hung up on the some ideal of how Redux is and will work for them while the reality is often quite different.

I actually made pretty extensive use of the redux time travelling debugger at my last job, I found it to be very convenient, especially as the application got bigger and more convoluted - not really because of development issues, but just general feature creep (features were being sold left and right by a less than stellar marketing team)
Yeah when using React (or Inferno, etc) I default to Mobx.

If you like that model, Svelte stores are very close to that as they are really reactive primitives. Honestly I think it was a terrible idea to call them stores.

SolidJS is also built on this idea of using reactive primitives. In this regard it's even better than Svelte as you get true fine grained reactivity.

The "state management problem" appears to be an invention of React. This didn't use to be a problem with MVC. The state lives on the server. When state changes it should be persisted on the server immediately (in case the user unexpectedly closes the browser). On the client all you need is a cache.

With MVC, each page is more or less independent. Each page gets the data it needs from the server (through a client-side cache), then updates the server when something changes. No "state management problem". In contrast, ReactRouter sees the entire application as one humungous component. Therein lies the problem.

I came to understand MVC (model-view-controller) from writing Objective-C desktop software (https://developer.apple.com/library/archive/documentation/Ge...).

The “M” in that MVC is “Model”, which is the same idea as “state management”. It’s an object that stores state and notifies listeners when it changes. You can think of these libraries as ways to implement the “Model” concern in the application.

But from your use of MVC, I think you are referring to server-side MVC, where the “V” is a rendered HTML template? I’m not sure how to square the desktop software version of MVC with your assertion that state should only exist on the server.

My Cocoa desktop apps ran fine 10 years ago without a server. The React application I work on today has many bits of local state it needs to track like “what is selected?”, “how wide is the sidebar?”, and “should this menu be open?”. I don’t think the server should be involved in such matters.

This is true until your customers complain that your UI is super slow. You realize they’re trying to use your app from a cell phone with poor service. So you have the genius idea to add optimistic updates in JS. Now you have all the problems from the article since you need to update all the components everywhere on screen that share state.
All you need is a client-side cache.
How do changes to that cache get displayed? Your cache emits events that each UI element has to listen to? Congrats you’re using redux!

(It would perhaps be more accurate to say that React folks had to reinvent these patterns, but the problems were definitely present in the postback era)

> Your cache emits events that each UI element has to listen to?

It doesn't. How often do you have the same information redundantly displayed in multiple places, on the same screen?

It's not even necessarily the same information, but small pieces of one bigger piece that need to be updated once some of the other smaller pieces change
Yeah this. The classic is unread state in a mail app. Unread state is usually displayed both as bold state per message in a message list, and as a count in the folder list. These need to stay in sync as the user reads mail (and sometimes marks read state explicitly). Two views of the same underlying state.
Sure, you can come up with an example where some synchronization is needed, but how often do you have this requirement? In most apps it is rare, which means that it can be solved by using a couple of extra lines of code to update the screen, as opposed to adopting a complex state management system with events firing and so on. Simple solutions for simple problems.
It is quite common. Take this screenshot of Youtube: https://imgur.com/a/nxGdcWj

The comments total at the top and each of the thread counts need to be kept in sync with their corresponding inline list representations.

Most lists in apps display a total count summary. This is even more common on desktop, where you have room to display more info.

That is exactly what the React state management solutions all are...
> Now you have all the problems from the article since you need to update all the components everywhere on screen that share state.

If anyone can field a question about react-query here, this seems like one of the exact problems I thought it solved when I started using it.

I do enjoy using it, but requesting the same data with the same key+queryFn from multiple, unrelated components still generates regular requests at the configured interval (even if I'd want/expect those components to share the response and only make that request at whatever rate satisfies the shortest configured interval)

Am I missing something in my configuration here?

React-Query is very aggressive about refreshing the cache by default, including whenever the browser receives focus, on a set schedule, and automatically retrying failed queries multiple times, these can all be disabled. If any queries use the same key they should share data, but I haven't experimented with it enough to learn all its intricacies.
Sure, all the other settings work fine (refresh on focus, failure retry, etc.), I just would have hoped to limit the requests made when the same data is requested in many different places.
Try checking out the options:

      refetchInterval: false,
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      staleTime: Infinity,
I was similarly surprised at how 'chatty' react-query was by default, but changing these defaults quieted it down. Great library though, and I understand the argument for these defaults.
Your causation is reversed.

People demanded more interactive web apps, which led to more stateful UI, which led to React.

In the case of Facebook, the motivating app was Ads Manager — an enormous, highly interactive single page app for advertisers. You couldn’t build something like it with HTML + server endpoints; in the pre-web days, apps with this kind of complexity would be desktop apps.

Google Docs, Google Sheets, Maps, Spotify — these all have a huge amount of client-side state. You can’t represent that state on the server without making the UI unusably slow. The need to build these kinds of apps in a portable way led to complex state, which React tried to address (so did Angular, Ember, Backbone, etc.).

> You can’t represent that state on the server without making the UI unusably slow.

And that's why you use a client-side cache.

And that's why you need state management. Cache is state.
And once that cache is sufficiently complex, it's just a state store like redux.
Cache stays simple even for large applications. It never gets "sufficiently complex".
Theoretically, but not practically.
As the joke goes, there are two hard problems in software:

1. Naming things.

2. Cache invalidation.

3. Off-by-one errors.

Caches are tricky beasts. First, you need to update them when the server-side state changes (for example, two people editing a single Google doc). Second, writing state changes to the server and waiting for confirmation is too slow for many kinds of interactions. In these cases, it's typical to optimistically write local changes to a local store, and then try to sync those changes with server asynchronously. But if synchronization fails, then the client needs to report that.

And finally, there's the problem of partially loaded state, where the rest is loaded asynchronously on demand. That should be simple, but without some kind of framework, it also tends to have lots of subtle state-machine and null-reference bugs.

So, no, cache does not always stay simple in large applications. (Unless the app is read only, and you're OK showing stale data.)

If you are building Google doc editor by all means, design a complex state management system, it is worth it. But then don't bring that complexity into typical business applications.
It’s all true, but you missed the part where react and/or redux are talking to the server to manage the cache. Because if they aren’t, it’s unclear why tf they are holding the mic.
> People demanded more interactive web apps, which led to more stateful UI, which led to React.

Except they didn't. 99% of web developers work on crud apps, not Figma. The users never really cared. You can achieve "good enough" results waiting for server. I still see over fetching from server instead of updating caches in most frontend apps anyway. Things like a button optimistically updating, just show a spinner and wait for the server. Devs just want to overcomplicate things just because they don't know any better.

I'm in an organization which had some extremely old, all-server-side-PHP tools.

We managed to get one section of them retooled as responsive HTML/PHP design, and then went whole hog on React.

Every single piece we replace with React seems slower and more complex. Tools that were "render it on the server as a finished page" has been replaced with "do it as an API that returns a ball of JSON and the client will render it." Most of these tasks are classic CRUD. So in the old version, you'd click a button or link and the page would reload in 5 seconds. Now, it lets you stare at a spinner for 10 seconds while it repopulates the same page. But golly, we avoided that flash of blank screen!

We were sold that splitting off an API would allow alternate implementation-- power users could bypass the UI and build their own automations. Nobody has; I don't think the API even firmed enough to be worth documenting.

I think the thing I resent most, though, is the tendency of modern Javascript to be a fantasy language. It does not exist in the wild. It's not like you can even say "here's a cool trick that works in Mozilla but not IE6" like back in the old days, it's literally zero-real-browser support. You instead need to set up an entire dev toolchain of stuff like Babel and Webpack before you can even get to Hello World. This seems like such a loss, given that the web was such an accessible platform-- get your $2 per month shared hosting, start writing a single file in PHP or raw HTML, and you can actually see useful stuff on your screen.

Both things are true: people wanted more interactive apps; at the same time, most engineers don’t work on those kinds of apps.

A frequent analogy: React raises the ceiling for the kinds of experiences you can build in a browser.

But not every app hits that ceiling. If CRUD works for you, do that. The simpler the better.

> In the case of Facebook, the motivating app was Ads Manager

Google created Flutter and its native language Dart in the process of developing AdWords. I don't know if it was the 'motivating' application for Flutter+Dart, but the first public exposure of Flutter happened less than two years before mobile AdWords was implemented with it.

I guess selling ads it a pretty complicated business if you need to create frameworks and programming languages to pull it off.

I used to think the same thing until I considered that single page app development is really just a reinvention of thick clients.

When you look at an SPA as a thick client state management is a natural thing as it was in Java swing and WPF and Windows forms and other stacks beyond my knowledge

React wasn't the first complex frontend framework, arguably people were having these state management problems ever since people started doing more stuff like jQuery UI. It's not tied to SPAs either, you can have state management problems for a single URL as long as it has any form of state change (like opening a modal).
For me the issue of state management mostly went away after starting to use React Query. It turned out that the most problematic state was server-side state for me, which is handled very well by libraries built for that purpose.

The state that remains is often simple enough that plain old React state management with useState/useReducer is sufficient. A lot of state can be local, and local state is easy to handle with the built-in tools of React. And global state is only really an issue if it changes often. For mostly static state like information about the current user, theme or UI settings and similar kinds of state using React Context works perfectly fine.

Of course this depends heavily on the kind of web application you write, if your application is closer to Photoshop in the browser than a simple CRUD app you probably can make good use of more complex state management libraries.

This matches my experience as well. Additionally, React Query has support for features like retries and caching.
React query is one of the highest quality react libraries I’ve ever used

  The state that remains is often simple enough that plain old React state management with useState/useReducer is sufficient
hah this is an understatement. For hooks, the React runtime already does a ton of non-standard-js things under the hood. useState gives a method to update and "subscribes" a component to updates on the hook's value. Put that code in a custom hook, export it and you re-invented Redux. In fact, exporting a single big useReducer for all your state gives you something almost identical to old style Redux.
Agree completely, React Query has made it so much easier to build any kind of app that deals with server state, stuff that would have been extremely challenging to write in the past is now just a couple of lines and works better than before.

I personally like using Jotai (if I just want a better version of React Context) or Zustand (if I need a bit more than that) for “client state” alongside React Query, but I’ve also built projects where I didn’t need a client state management solution at all.

What’s wrong with React Context in your opinion?
For anything even slightly complex you need to either use multiple providers or be very careful about rerenders
Exact same experience here. We were using Redux to manage server-side data and switching to React Query massively simplified everything. It appears that managing server-side data in an efficient way is an insanely complicated topic and React Query just solved everything about it that I can think of.
Yeah, both the React Query and Redux maintainers agree that you shouldn't be writing data fetching and caching logic yourself. That's why we recently added a new "RTK Query" data fetching and caching API to Redux Toolkit, so that if you're using Redux it handles that work for you:

- https://redux.js.org/tutorials/essentials/part-7-rtk-query-b...

- https://redux-toolkit.js.org/rtk-query/overview

and if you're just using React, definitely look at React Query.

Yes we chose Redux 6 years ago for data fetching and caching when there wasn't as many alternatives as today!
Hah, yeah, the ecosystem landscape has definitely changed over the last few years :)
RTKQ is terrific. Between things like that and MUI, the level of productivity you can achieve really is amazing.

Also, thanks for your work -- really, really appreciate it.

I've only used SWR. How does it compare? I
SWR does basically the same core thing (query, cache and mutation help) with less features.
That's exactly the reason why I choose SWR over ReactQuery.
I took a look at react query and it seemed to me that the core of it was caching the network request promises with named keys, so that you could inefficiently call the same endpoint redundantly a bunch of times and not get punished for it. Even if a codebase could get away with such sloppiness it should probably avoid it.

And then as I recall it featured some out of the box refetching to unstale the cached data. I think this core idea is pretty good, though I don't like the cultishness of the people who use this lib. And the vast majority of pages in the wild I have worked on don't need this auto-refresh, as the data will be static until the user does something new (i.e. because the user is viewing personal data). Even if I wanted to refresh the data often (i.e. I had a site like reddit) I would prob just roll my own custom refresh logic.

And then this

https://ui.dev/checkout/react-query?from=tanstack

If server side logic is "insanely complicated", and is routinely being solved in two lines post react query, instantly working better than files worth of server side data fetching code, why the need to charge 149 to learn the fundamentals? If I had taken a paid course to learn a state management library I would probably advocate for it everywhere I went.

It's a great benefit for a component to be able to specify what data it needs from remote without worrying about if an ancestor or sibling also needed it. React query will only fetch once the unique queries in the whole tree.

Without this feature, every component needing remote data D must share a parent ancestor fetching D for all of them, even when the children are not conceptually related. Adding another component higher in the tree means you have to hoist the fetcher up to that level, etc. React query is an incredible upgrade to anything else I've used over the years.

> must share a parent ancestor fetching D for all of them

What happened to the whole "service" pattern? Isn't this the core problem it solved for Java in the 90's?

An external service fetcher doesn't have integration with React because the entire tree potentially needs to know when the data changed. Once you write the code to subscribe to those changes where necessary, you've basically invented another state library.
* Service fetcher does fetching and updating store

* Component does subscribing

* React does updating component tree with changes.

If a component wants changes it must subscribe. If not, it doesn't.

If there is a change, subscribed components get updates. If not, they don't.

Responses are (conditionally) cached in the store by its respective service. Any component that wants data just asks the service. Service fetches from the store or remote. I can customize and unit test the the service.

I don't see the overlap in responsibility between any state lib (react or whatever in this case) and the purpose built service.

What is react query's role here ?

You’re hand-waving away “subscribing” as if it’s not a time bomb.

The set of possible events any given query might need to subscribe to is massive. Yes, you can build a large app with a pub/sub model but the number of subscriptions runs away from you and it gets really hard to know which of 1000 subscriptions you need to refresh when an association between two random pieces of data is created/deleted.

Associations are where you really get screwed IMO.

That said, despite exploring lots of possibilities I don’t think there’s any perfect way to do this automatically. So while I think you’re hand-waving away a hard problem, I also think your proposed solution is probably one of the better ones.

Still, I don’t think you should pooh-pooh people trying to solve the general problem. It’s a worthy one.

That's a sensible response, so thanks for that.

Being hard to define and manage subscriptions is a problem, a bigger deeper problem that react query or any other lib isn't solving.

By "set of possible events any given query might need to subscribe to is massive" i assume you meant "component" and not query, if not then i don't know what that means, if yes, that component needs refactoring.

I'm interested in libraries not because of what it can do for my code or me or my team but what problem it solved for the team that needed it in the first place, bad enough to write a lib for it. I want to avoid those problems.

I start with importing libs for quick TAT and slowly replace them with a few hundred lines of code that i can understand, modify, debug, monitor, unit and integration test. I usually end up redefining or better defining the problem and rescoping the issue to not needing to solve said problems.

Most libs do NOT provide that level of valuable solutions to worthy problems to go keep them around as deps. Deps are not free and have no liability to me or my code. React query is one more in that long list. IMHO.

I do mean “query”. I am presuming you want every query in your app to live update when the results change. The problem I’m indicate is: how do you know which queries are invalid after any given event?
> What is react query's role here ?

Its role is to do everything you just said in one package, plus other networking features. It fetches, updates the store, subscribes the components, caches the responses, performantly rerenders. It allows configuration of when to refetch, how to cache, polling, pagination, infinite scroll, etc etc via a simple API.

Nothing is stopping you from writing all this yourself, but libs exist for a reason. It's a terribly useful networking package. If you just use a generic store and write a fetcher yourself, you have to at the very least write logic for when to (re)fetch and for persisting the responses to the store.

React Query's critical design flaw (from when I used it, anyway), is that data is scoped globally by default, as opposed to per mount.

Using the global cache is kind of the point, yes, but that's implicitly the case and that's the exact opposite of how useState works.

I think that is an egregious mistake, since it is totally unclear to a higher level consumer that this is the case.

This may seem unreasonable, but I've seen it cause severe issues due to this choice and I would recommend against it for many teams unless they can properly communicate that it does not match the behaviour of useState - you need to look through layers to be convinced it's going to do what you expect.

A simple flag to opt-in to the global cache associated to the provided key would solve a lot of problems.

To summarize, I like the API they provide a fair bit, but it's just too easy to misuse and create a damn mess.

data is scoped globally by default, as opposed to per mount.

Uh, is the cache key the same? To me it is 100% clear that the cache key is used for a global cache, and that this is one of the core design principles of the library. This global cache management is exactly what I desire over useState without writing my own boilerplate.

I personally liked the library for its query and mutation APIs, but would have preferred caching be left out or opt-in.

I ended up having to do this myself by wrapping it all up and providing an option for the caller to opt-in to the underlying global key. If the option was not passed, it would append a unique ID to the base key by default so it would become scoped to the particular mount.

IMO that should be the default behaviour.

Nothing implicit about react query caching requests. That's the whole point of the library in the first place, otherwise it would just be a fetch wrapper.

And having it behave differently from useState is fine, and correct. The library is used to fetch data, not state. Those are two different things

I disagree with the entire premise of the library then, because I consider a shared, global cache to introduce nothing but problems.

I like its query and request primitives, but I don't like that they are coupled to the cache.

It’s purpose in my mind is to model server state, which is fundamentally global state.

Are you using it for transient state? What are you using it for that’s meant to change between different parts of the app?

It kind of sounds like your namespace needs to be broader, are you using a singleton where a collection is needed?

I used Redux and loved it until I moved to typescript. Then there was a terrible amount of boilerplate and magic. So I wrote my own 75 line alternative that does a bare minimum. It’s basically just a useContext wrapper. And I haven’t looked back since.

This is for pure client side stuff, of course.

Hi, I'm a Redux maintainer. FWIW, we specifically designed our official Redux Toolkit package to not only eliminate the general concerns about Redux "boilerplate" [0] [1], but also work great with TS. With our recommended RTK+TS usage patterns, a typical "slice reducer" file only needs to define a type for the reducer's state, and then define a case reducer as `(state, action: PayloadAction<MyData>`) [2], and that's it.

We've put a _lot_ of work into making sure that our library TS types minimize the amount of types that you have to write in your own app code.

Also, one of the reasons we now teach the React-Redux hooks API as default is that it's drastically easier to use the hooks with TS than the legacy `connect` API.

If you haven't had a chance to see what "modern Redux" looks like, I'd suggest going through our docs tutorials to see how we want people to learn and use Redux today [3]

[0] https://redux.js.org/introduction/why-rtk-is-redux-today

[1] https://blog.isquaredsoftware.com/2022/06/presentations-mode...

[2] https://redux.js.org/tutorials/typescript-quick-start

[3] https://redux.js.org/tutorials/index

RTK is what made redux usable for me. I can't imagine using redux without it. ^ Mark is also very active and responsive in the Reactiflux discord and has directly helped me and countless others clear any hurdles with its use
Hooks made react and redux far less ridiculous. Class based react and redux wss maddening.

I’ll poke again but last time I tried I couldn’t avoid Typesafe Actions library.

Yeah, _please_ don't use `typesafe-actions` :)

It may have had some value before RTK came out, but a lot of the opinions and approaches shown in its docs lead you to write _wayyyy_ too much code. For example, we specifically recommend _against_ writing TS unions for action object types [0].

RTK completely obsoletes `typesafe-actions`, and the TS usage patterns that we teach today should result in a pretty minimal set of types that you need to write in your own code.

For a small example see the RTK+TS template for Create-React-App [1]. If you want to see what a real app codebase can look like, the client app for my day job at Replay.io is OSS [2]. It's admittedly a somewhat messy codebase due to its long evolution and legacy (started as the FF DevTools codebase, copy-pasted, and we've been slowly migrating to RTK+TS and modernizing it), but files like [3] show how I would write a real slice reducer with RTK+TS.

[0] https://redux.js.org/usage/usage-with-typescript#avoid-actio...

[1] https://github.com/reduxjs/cra-template-redux-typescript

[2] https://github.com/replayio/devtools

[3] https://github.com/replayio/devtools/blob/454804188d33900a26...

Context and Redux are somewhat different tools and context doesn't necessarily solve the same problems as Redux. This article by the maintainer of Redux (acemarke) goes over why (looks like he replied to you as well) [0]. Have you tried Redux Toolkit as well? It cleans up a lot of the complexity of Redux and works well with TypeScript [1].

[0] https://blog.isquaredsoftware.com/2021/01/context-redux-diff...

[1] https://redux-toolkit.js.org/

Basically I’m not saying redux is bad. Just that after years of using it for production software I concluded it’s still overkill for my needs.

IIRC Redux is also just an abstraction on top of Context. Fundamentally it gives you pseudo-global access to application state by being able to interact with it anywhere in the component tree below the context manager.

> Redux is also just an abstraction on top of Context

No, this is a very common but incorrect misunderstanding of how Redux works.

It's true that React-Redux does use context internally... but only to pass down the Redux store instance, _not_ the current state value.

Also, because Redux itself is separate from React, there's a lot of things you can do with it that are completely different than what Context does. _One_ bit of overlap is that both can be used to access state across the component tree, but Redux does much more than that.

See my post here for more details:

https://blog.isquaredsoftware.com/2021/01/context-redux-diff...

I posted that same article above, I'm not sure if they didn't read it the first time that they'll read it the second time, lol

By the way, thanks for your hard work on Redux, appreciate it.

I've used Redux but I've never heard of any of these alternatives. I have no doubt they're popular, but what is it about frontend that makes everyone reinvent the wheel every five years? Everything from the tooling to the tiny details somehow expires and gets recreated in a similar-but-not-similar-enough way that keeps the ecosystem in a constant state of flux.

Is it the lack of platform API support? Is it the community trying to make everyone a library developer? What's wrong with frontend?

I think it's because redux is quite painful to use with very modern apps. Most of the time you need something like `redux-saga` or `redux-thunk` to deal with async side effects. I'm not sure which is the most popular today, but sagas are based on generators and trying to use those with typescript is very very painful and the underlying issue [1] is marked as a design limitation in TS itself.

In addition, if you want to have your app load as smaller chunks rather than a single large bundle, you need to be careful to ensure that things work even all the backing reducers aren't yet loaded.

1. https://github.com/microsoft/TypeScript/issues/32523

In my experience the experience with Redux Toolkit is awesome, it's essentially an opinionated way to use Redux for modern web applications and I believe they really hit the mark when it comes to reducing boilerplate and the need for extra deps. Although I'm still not a big fan and prefer to use built-in tools of React to manage state when possible (now that we have Context, Suspense, etc.)
You need redux saga because api interactions can be complex.

I need to update a remote resource. But wait! Sometimes that can fail. I need to capture the failure logic and make appropriate ui changes. There are 4 types of errors and they require doing some library logic to figure out what to display.

But wait! I want to wait 250ms before triggering any state updates, or else the ui transitions will feel buggy.

Oh, and product now wants to make sure we capture some random third party tracking event in between specific state changes. The ui MUST NOT change until the tracking call succeeds.

This and more is trivial in redux saga, and you can write tests around it.

For the record, we actually have recommended _against_ using sagas in most cases for a long time now, and especially for data fetching.

Today, our recommendations are:

- Data fetching: default to using RTK Query, fall back to thunks if needed

- Responding to actions or state changes: use the new RTK "listener" middleware as the main approach

See my recent talk "The Evolution of Redux Async Logic" for details:

- https://blog.isquaredsoftware.com/2022/05/presentations-evol...

as well as my recent presentation going through "Modern Redux with Redux Toolkit":

- https://blog.isquaredsoftware.com/2022/06/presentations-mode...

Oh my. What is this ecosystem where the maintainers are downvoted for explaining the current recommendations for those using their library?
I was called in to rescue a redux-saga based project that had gone badly off the rails. It was one of the worst codebases I've ever had to work with in many years of coding. Stack traces were useless and debugging with anything higher level than log statements was impossible.
Apparently you don't need to debug if you're a good programmer. This explains why many modern tech stacks (JS, Scala, Kotlin, ...) have a horrible debugging experience.
We specifically moved off thunks because the capricious whims of changing network calls with logic to support it is so much easier in redux saga. Stuff just works. You can handle every edge case.

And now there are multiple deployed production apps with real users, so it’s almost certainly never going to be replaced. The cost would be enormous.

Yeah, I'm not questioning whether you _can_ do things with sagas, and there's definitely cases where their capabilities are valuable.

But as I put it in my "Evolution" talk: "Sagas are like a chainsaw. Really powerful, and really dangerous. If you actually _need_ that power, great! But most of the time you don't actually _need_ to use a chainsaw on a daily basis."

As the sibling comment said, a lot of times sagas really do end up as spaghetti code, largely because they are so event-driven, and with all the use of generator functions that can make debugging hard. (Ironically, the original Flux Architecture was created to _avoid_ the problems of Backbone-style event triggers causing events to ricochet around the app without a way to understand how things would update in response, and sagas can end up recreating that problem with Redux apps.)

Saga is so heavy and overly complex and you don't really get much out of it if you understand how middleware works. There's really no reason to add a huge dependency to your project.

You can do all of that pretty easily with a vanilla redux.

arcanis has a PR to patch up the issue that's sat for a year plus now. https://github.com/microsoft/TypeScript/issues/43632

> In addition, if you want to have your app load as smaller chunks rather than a single large bundle, you need to be careful to ensure that things work even all the backing reducers aren't yet loaded.

In the decade old application we use redux saga in at work, a large portion of our 7MB minified/gzipped main chunk is redux handlers. It'd be nice if there was a relatively simple way to not synchronously load all the reducers up front.

We have docs on code splitting reducers and other Redux logic here:

- https://redux.js.org/usage/code-splitting

There have been some different community packages for helping with that process, but some of them seem to have become outdated (only worked with React-Redux v5, etc). I did see a new one at https://github.com/fostyfost/redux-eggs that seemed like it had potential, but I haven't had a chance to try any of them myself.

I also once saw someone play around with the idea of using React's still-not-technically-final Suspense support to help ensure that a lazy-loaded component that relies on a code-split reducer doesn't actually get rendered until that reducer's state is available. Don't have the link handy atm, but if someone wants it ping me and I can go figure out where that was described.

Also, the new RTK "listener" middleware was specifically designed to replace almost all saga usages, and you can dynamically add more listeners at runtime via dispatch an `addListener()` action:

- https://redux-toolkit.js.org/api/createListenerMiddleware

- https://blog.isquaredsoftware.com/2022/05/presentations-evol...

You don't need _any_ of that. You can use vanilla redux and put your asynchronous code in middleware.
More of this. Middleware is simpler to grok than reducers and provides a clean, universal solution to async. You don’t need sagas, just dispatch events at meaningful points in time, like when a request is initiated and then another action when it completes. Pretty sure Dan Abramov taught this approach years ago in an egghead tutorial, if it takes hearing it from the horse’s mouth to get you to consider not just throwing more libraries at it
"What's wrong with frontend?"

This is the age-old question. You're using Redux now, but I'm sure some JQuery/Angular/Knockout/etc dev said the same thing about what you're using back then.

The front-end is crazy and I don't think there's an obvious answer of why it's such a developmental disaster. It's easy to say it's run by script kiddies or the barrier to entry is too easy, but there are a lot of smart devs working on the top libraries. The culture just ended up this way. Whatever is driving it probably will never stop though. Enjoy the ride. lol

Frontend end is crazy because many UIs that do useful things are inherently quite complex. UIs, by definition, are how people interact with software to solve problems and accomplish goals. The problems we solve and the goals we achieve with software are continually evolving and expanding as more and more software is created.

The people-software interfaces for many of the commonly used pieces of software will always be complex as we will always demand a lot of our software, up to the limit that can be provided by the available tools for building these apps. As our capabilities for building better software grow, so too will the demands of the users.

If you aren't buying this argument, sit down and map out every single possible state and every possible event (user interactions, etc) of a moderately-sized app that you use regularly. Or even just one page of that app. There's a lot going on. It feels simpler than it is when we are using it because well-designed applications become invisible to the users, especially as we become familiar with them. They "just work".

TL;DR Reality is messy and complicated. And so is the software we build as well.

A few different problems...

"Necessity is the mother of invention" comes to play here. The reality is that the community hypes certain tools, but in practice, they tend to have gotchas buried far beyond the surface level demos and documentation. The problem with that is that you only figure that out after committing to those tools and using them. This leads to tool abandonment, or in some cases, developers taking a swing at their own version. What they come up with is more often than not a rehash of the old ideas but lacking any "why" or long-term vision.

The other one is employability by obscurity. An old grifter trick is to make something far more complicated than it needs to be as a means to guarantee employment (both on the tool developer side and the end-user side). For the tool developer, the more they can twist and turn their tool (introducing novelty and potentially confusion), the more sought-after their services will be. For the end-developer, they can hold a "monopoly on intelligence" and become difficult to replace in a company because they're the only one that understands that thing. Couple this with the conference talk circuit where you see the same people constantly pitching some new-fangled widget every year and you realize the goal isn't to solve the problem, it's to get paid to look like you're solving the problem.

Another problem is inexperience. A developer might have just enough experience to feel confident at the code-level, but they lack the practical experience to let them know why a certain pattern is incorrect. Assuming that they never get that practical experience, they will continue to iterate the tool into an utter mess or deprecation.

I can assure you nobody at my workplaces cares what tool I'm using. I'm the only developer on my team. I am still looking for better state management solutions.
I considered something like this last night as I stay up to 6am working on a new UI library. My conclusion was in part because it's easy. The goal is well defined. I got the first widget working thought I'd call it a day (a night) but then it was so much fun to just implement the next widget mostly because very little design iteration is needed. The problem being solved is well known so each thing to tick off my UI lib was only a few minutes. Next thing I know it's 6am.

I think the same is true of 3D libraries. The real work of making an app that does 3D is not the 3D engine, it's everything else (UX or GameDev). The 3D part has well known goals and solutions so it feels like you're making a ton of progress, ie, it's fun to make. Much easier than deciding harder things like which features your app should have and how they should work.

(comment deleted)
Great insight, this makes a lot of sense. And on top of this, it SEEMS impressive. You made a whole framework! Wow! Plus there’s a chance of widespread adoption—-so, well-defined goals, fun to work on, seems impressive, potentially very high payoff. No wonder there are so many.
It's the case in the backend and devops world too. But like for Redux you probably chose some decent libraries and tools at the beginning and sticked to it. If you start looking around you will see there are alternatives to everything you use, and even maybe that most of the industry moved to those alternatives in the past 5 years.

How many ways are there to build an API? In Python: Falcon, Flask, Django, FastAPI and so on. Just as many choices in Java, Go, Node, <insert your favorite language>.

Most of these new client side state management libraries are incompatible with any form of SSR. If you see the documentation for Jotai/Zustand/Valtio, the solution is to avoid using with Next.js at all, or fall back to hacks using context+provider at which point the state manager becomes effectively redundant.
Isn’t SSR only really relevant when you need to optimize for SEO (which only applies to a subset of apps)? Seems like it doesn’t confer any other significant benefit.
Think about how the benefits to SEO (page speed, web vitals, less client work) can improve overall app performance.

SSR is just one technique.

I'm sorry people are downvoting, because you are correct.

SSR (by which I mean Next.js) is the most over-invested in JS tech of all time. It introduces a bunch of crummy DX which never pays for itself from a business standpoint.

SSR is only potentially useful for landing pages. In which case you should be using Wordpress or something like Wordpress so you aren't wasting dev resources on something a marketing team should be doing. (If your argument is that SSR is necessary for speed, I'm pretty sure exporting your Wordpress site to static HTML + CSS and using Cloudflare could more than make up for the difference.)

The one asterisk I'll add to this is that SSR in the form of the Remix framework may justify its own existence as it removes the need for an explicit API layer, which lets you skip a ton of boilerplate code. This is a big win. The fact that Remix uses SSR is just an implementation detail -- it's the DX that's actually valuable (although SSR fetishists will probably still like it, too.)

And how exactly do you plan to avoid the flash of white without SSar?
I don’t. Because it’s not worth the hassle, as explained in my comment.

If I did want to avoid it I could send html for a generic loading state in my base html template, which does not require ssr.

If you’re using Wordpress or static site generators for your public facing pages you won’t have the flash, and if your user is invested enough to have made it past the landing page the flash will be a non issue anywhere else.

Good. Hybrid SSR for everything is the most overengineered bullshit fad I've seen in front end development. The sooner noobs stop cargo culting Next JS the better.
(comment deleted)
After reading the "Grug Developer" article recently, I've been thinking of frontend development as "The Plane of Eternal Complexity Demons".
This is because UI programming is inherently extremely complex, especially compared to something like a stateless API tier running in AWS. It’s stateful software deployed to countless different runtimes on hardware you don’t control. Instead of a smattering of API routes handling well-structured semantic datatypes like `POST /burgers?pickles=false`, input comes in the form of arbitrary UI events from various input devices like pens, keyboards, mice, etc that must be interpreted to have a semantic meaning. Often there are hundreds of API routes (event handling elements) on screen at once in a complex application. UI engineers are perpetually frustrated by this complexity, and so some small percentage look for better ways to handle these issues.

Of course it’s best to avoid UI programming entirely, but in many domains it’s necessary.

We went to the moon some +50 years ago. We ought to have solved "UI programming" already, no?

Something is not working

By that logo , SpaceX shouldn't exist.
Ultimately, successful products are determined by customers and by product managers and their executives, and it turns out there is no one-size-fits-all approach to even basic UI programming because everyone has an opinion.

Not to mention, as an example touch became a widespread new UI paradigm in the last 10-20 years.

simple problems easily becomes complex in mainstream contexts.. due to bazaar like soil. NASA had to solve one immensely hard problem but they owned the context. Vertical integration if you will.
UI programming is much harder than going to the moon. Do not confuse danger, scale, glamour, etc with difficulty
I disagree, I think UI programming is inherently quite simple. Especially with a paradigm like immediate-mode GUIs in my opinion. I believe the complexity comes from the fact that the DOM is a poor API for creating user interfaces, which ultimately inspired developers to create frameworks that work in a more “immediate-mode” style way.
The native UI toolkits are even harder to use than something like React and recently have been evolving to a similar reactive + declarative paradigm.

User interfaces are complex, poorly specified, and subject to rapid and often capricious changes in the middle of development. Don't blame the tools.

Bullshit. In 1995 people were creating desktop UIs order of magnitude more complex than today's anemic webapps, without any of the braindamage-inducing stuff that's happening in web UI development.
I was legitimately curious about this--my memory of Windows 95 is not this nice--so I looked at Wikipedia's list of software released in 1995:

https://en.m.wikipedia.org/wiki/Category:1995_software

What software specifically do you recall being "an order of magnitude more complex" than today's popular web apps?

Just from the top of my mind: Excel, Word, Photoshop, Windows Commander (called Total Commander today), SolidWorks, AutoCAD, Borland Delphi.
Excel is quite complex, but the Excel of 2022 is far more complex than the Excel of 1995, and part of it exists on the web itself, so Excel '22 would be an invalidation of your argument that '95 apps were more complex (as well as other modern versions of the other apps).

One major difference in 1995 applications is that almost none of them were collaborative or synced with a remote server. They were all isolated applications that worked entirely in a local context. They could be programmed to a single target platform on perhaps one to three screen resolutions (640x480, 800x600 and 1024x768). They could all work under the assumption that one style of input was being used (mouse and/or keyboard). They all could use simple built-in dropdown context menus. They could all render to a canvas context graphics with much of the heavy lifting assumed by the operating system itself.

We certainly had network programming in the 90s. It was just that apps didn't use it unless needed. SGI res was typically 1280x1024 and Wacom tablets were common in studio/professional settings.
Apps didn't use it because it wasn't practical or in high demand yet. People were just getting used to checking email and most people were only intermittently online via dial up. Collaborative apps would take another 8 or 9 years to really start to take off.
We had T-1 internet to every desk in ‘94 and I used dozens of networking apps from archie to veronica. Probably used CU SeeMe before a browser. Though Mosaic dropped around that time.

Advanced collaboration still in the future, this era was more like chat and file transfer. I think you could mark up Word docs from a network drive at some point.

Win 95 was a toy, NT was getting there. Apps were being ported quickly to NT4.

I do remember using Maya on Irix in '97 or so and it was already pretty amazing. It was built off earlier applications like power animimator, which was started in '88:

https://en.wikipedia.org/wiki/PowerAnimator

Maya had a customizable interface, and neat things like pie-menus, although not invented there.

There are definitely examples of UI programming that is more complex than certain APIs that largely perform basic CRUD operations, but I'd strongly disagree either is inherently more complex than the other, it's just that human behaviours and preferences are messy and unpredictable, which means conceptually "simple and elegant" UIs are often not what users actually enjoy using, and this inevitably has impacts at the code level. APIs don't have to designed to deal with human eccentricities as they're written for developers, and we aren't like normal people.
I find the big difference for me when building an API vs working on a UI is the testing. I think backend APIs tend to be much easier to test than UIs. For the backend, it's mostly Request in Response out, check they look good and the side effects (database, caches, queues, etc.) are performed etc. For the UI you have a lot of async stuff going on and writing non-flakey tests is tricky and verbose with the current tooling - using react testing library which is better than enzyme, but still harder than testing a Django endpoint.
Absolutely - realistically you never can automate UI tests fully, as there's no way of describing all the things humans "expect" to experience while using a UI. Even when you can, the effort involved in creating and maintaining the tests and confirming that the failures are genuine often doesn't justify the benefits.
I think this suggests that UI is more complex than API.
Then you've never written APIs that have to do super complex calculations or process huge amounts of data efficiently and reliably. By far the most complex code I've had to deal with has been "back-end engine" type code, even if it was technically part of a desktop application (but wasn't dealing with user interactions).
My point isn't that "all UI is more complex than all API", but that the constraints and nature of UI tend towards complexity (especially incidental complexity) at a higher rate than API programming. As you observed in a previous post, UI engineers struggle to use techniques like end-to-end testing that API engineers use with relative ease. Why is that? My argument is that engineers working in the API space more completely control their problem space in general. Some examples:

- Want to rigorously specify the interface of an application? In API space, we can use an IDL like Protobuf to write a contract of exact input and output types. In UI space, the best we have is plain-english text language called Gherkin.

- Want to record all inputs and outputs made to application to verify them against new version? With network APIs, we can often record all app-level IO in production using network capture; often with negligible performance impact. To do best possible job for deterministic replay, we can actually hook into our CPU if we try hard. UI? Sorry, again we're running on a customer device, unlikely we can apply these technique. At best we may be able to capture some UI change traces on a few devices with spare bandwidth and CPU.

- Having performance troubles? In API space, feel free to buy more hardware, or optimize more tightly to the existing homogenous platform. Oh, maybe application takes a few minutes to boot up and "warm up"; this is fine, we'll use blue-green deploy and replay a bit of the read-only traffic we captured earlier... In UI space? We probably need to consider 2-3 versions of ~5 different operating systems, running on myriad heterogenous hardware, to say nothing of browser issues; where every few weeks Google releases a new runtime with the potential of performance regressions. Slow to boot? Say goodbye to users; we need the app to paint in 1.7 seconds and be interactive in 3.8 seconds, on demand at any time of day.

- Have a problem with teams stepping on each others toes? In API space, you can try to deploy two services (not saying this is good idea, but plenty of people see it as solution to this problem); end users won't know the difference. Try to deploy two separate apps in UI land? End users will be sad.

- Need to handle large amount of data in API? We have low latency access to storage APIs that scale to petabytes. In a UI? We'll need to build an API first to mediate streaming access to that data and store it for us, since we can probably only store a few hundred megabytes locally before we run out of disk. In both API & UI we need to add a local cache, but how do we test local cache provides good experience? This takes us back to testing topic... where API seems to be easier.

An argument from anecdotal experience isn't worth much in this territory; I can easily same the same thing in reverse: the most complex code I've had to deal with is threading the very narrow constraints of contentEditable programming for Notion's rich text editor. Now let's add a dash of "no true scotsman" as well: You must have never written any such complex UI! See? I think this is an unsatisfying approach to the issue (and not one I make in earnest).

I don't disagree with anything you've written there, but I still disagree that UI code is typically or inherently more complex (or harder to write well) than backend code. I doubt it's anything you can even prove one way or another. But while the nature of inputs and outputs for UI code tends to be nebulous and somewhat poorly defined, it's hard to argue the range of possible inputs compares with code that has to process massive datasets.
It makes me smile that elsewhere in this thread there's people saying "I've been using <library> and state management is now super easy." when you know in two months people will be saying that library is over-engineered and someone will suggest to try the new panacea that will solve all of your problems, for real this time.

Such is the hype cycle of frontend development.

I mostly use Qt Widgets which hasn't fundamentally changed since 1995 and still performs just fine for desktop UI
Link: https://grugbrain.dev/

It is universally worth your time to read it if you haven’t :)

Plain english translation: https://github.com/reidjs/grug-dev-translation

I haven't read it, but wish I had seen it before struggling through the original

Thanks so much for the link! I tried the original and bounced off the style. It seemed like something that might be fun and useful if I invested enough effort in reading it, but I just bounced off.

But with the translation, I just devoured and appreciated it.

(comment deleted)
JavaScipt on the browser is cursed[1].

1. There is no standard GUI library, as per article. A GUI can be as arbitrarily complex as you wish, and every different frontend system is a unique GUI system deployed in JavaScript, usually using HTML for the view drawing primitives* and events for input. The complexities of GUI library design bubble up to developers who are implementing their own tweaks or combinations of a GUI library, usually with a huge amount of “needless” variation. Application developers should ideally never have to be making choices about internals of a GUI system, yet the core of most articles comparing frontend frameworks is discussing GUI internals.

2. There is no golden standard for tooling. Everyone has their pet variations on how to deploy to JavaScript, CSS, and HTML.

3. Back end choice. Huge variation.

As a developer we get APIs on the edges, but we make our own spaghetti to join everything how we wish because there is not one or two standard library/framework choices, and we have the power to do what we will. I developed my own 100% custom component framework because I could write one that suited us far better than what was available at the time (OSS or commercial). Browser variation used to be a huge driver for complexity, but is far less so now.

* drawing primitives can also be Canvas or SVG or WebGL e.g. https://news.ycombinator.com/item?id=27131659 is a good comment.

[1] https://www.google.co.nz/search?q=lisp+curse

At least in AI, what happened to lisp was being replaced by Python. What do you presume happens to JS in this case? WASM frameworks are likely to have the same problems. Stuff like Phoenix Liveview can be rather standard but limited in scope.
> never heard of tools

These libs are at 4.7k, 8.9k and 17.1k stars on GitHub...

> discuss the deep inner workings of things

The article is a few thousand words and there are a few interesting reflections...

> of things that already exist

The point of the article is to actually talk about "new wave stuff"...

You are arguably incorrect on many fronts, but more importantly, did you have to spend this many words being an utter dick to the author?

Or you just manage state in the server. With https://inertiajs.com/ you can even afford to not to declare an API and still manage the state back there.
>Or you just manage state in the server. With https://inertiajs.com/ you can even afford to not to declare an API and still manage the state back there.

Then we can party like it's 1999!

Really surprised there was no mention on MobX. I've used int for lots of projects and loved it.
I'm pretty sad this is the point we are at with React frontend development, the library is becoming much larger than it's original scope and it's getting bloated while not solving the essential problems that happen. In my opinion the best solution we have right now for frontend JS development is to not use JS or JSX at all and instead use a DSL such as Svelte, this way the compiler abstracts all complication and there is no application bundle size bloat. Obviously we have Preact (which solves react-dom bloat), MobX (which solves state management), etc. which are all great tools but the problem is that every tech stack is different and the more the core technology can do without getting more bloated the easier it is to introduce new developers to a codebase and keep up with changes.
Svelte does wonders to code size, indeed.

But does it solve the problem of growing state which never gets GC'd when components which used to use it are gone? Does it solve the problem of only redrawing the required minimum (beside the normal VDOM approach)?

I'm asking as someone not knowledgeable enough about Svelte.

> Does it solve the problem of only redrawing the required minimum (beside the normal VDOM approach)?

Yes, in fact I think it's actually pretty hard _not_ to redraw the bare minimum LOL. If you want to know more about Svelte (even if you're not looking to develop with it) I HIGHLY recommend listening to this presentation called "Rethinking reactivity" by Rich Harris (the creator) https://youtu.be/AdNJ3fydeao

> But does it solve the problem of growing state which never gets GC'd when components which used to use it are gone?

I think the answer to the question is yes. Although the problem I'm speaking mostly about state management is the source of truth problem. Svelte provides a global store to store data, and data stored on local components are just _variables_ (no useState or hooks or anything complicated) where the Svelte compiler handles everything.

Something else I'm really eyeing right now is SolidJS which takes a very similar approach to Svelte (compiler instead of library) for frontend development but provides an API that's very familiar to React developers so there's not much of a learning curve (although Svelte has a very easy learning curve too).

This is why I'm building Joystick: https://github.com/cheatcode/joystick.

It riffs on the old, simple APIs of React but uses pure HTML, CSS, and JavaScript w/o any trickery (I'm also hardcore about not changing the component API so WYSIWYG).

The bonus is that it's a part of a full-stack framework (the UI framework has a Node.js counterpart), so wiring up a full app is near-effortless.

Looks pretty interesting, I'm always kind of bothered about the lack of (true) full-stack frameworks for the NodeJS ecosystem that handles accounts, database, frontend, API, etc. like we have for Python with Django or C# with .NET.
Thanks. Likewise, and that's the driving motivation here.
How is React not solving essential problems? Suspense solves a pretty essential issue. Also Svelte can quickly increase in bundle size and if you have enough components, it can become bigger than the equivalent in other frameworks: https://github.com/yyx990803/vue-svelte-size-analysis.
The first version of React was released on 2013, it took almost 10 years for Suspense to exist (we _just_ got it now with React 18), that's what I'm talking about. Even functional components and hooks took a lot of time from them get and implement the idea after they tried to use ES classes and made everything much harder to manage. Context also isn't perfect, I like it but the redraw performance is not amazing and doesn't scale at all to bigger applications.

> https://github.com/yyx990803/vue-svelte-size-analysis

This is an interesting comparison I haven't seen before, I wonder if it's true for a complete application using some lib for state management, routing, etc. and if this isn't just a kind of cherry picked example. Thanks for showing this though.

I've been running into nasty performance with setRecoilState and large piece of state firing multiple times per frame.
"Prop drilling". So many buzzwords. SSR, Hydration, Tree shaking, Hot module reload.

How has web development gotten here? It just seems to have evolved so much needless complexity to me.

Don't forget about web components! Integrated into the platform.

I've often found those that complain about these buzzwords are the people who do not actually do any frontend engineering. There are buzzwords in every language and every library. It's not an indictment of complexity (which exists outside of any one language or library), it is simply the terms of the trade of that particular technology.
I don't know why but front-end terminology always makes me cringe. I know it's useful jargon. I don't know why I have a visceral distaste for them. Maybe because it feels like taking a small, simple thing and making it seem like it's something more.

But you're right. I haven't done much front-end at large scales. Only at one employer for a short while.

> Maybe because it feels like taking a small, simple thing and making it seem like it's something more.

Perhaps it makes more sense to treat frontend engineering as thick client desktop development of yore. Websites are no longer small, simple things, they are now the primary apps that many people use (through the broswer and especially through Electron), so there needs to be sufficient tooling around managing that complexity.

You’re right. That makes sense. I’ve been out of web dev for about 4 years so I’m just reactionary now I guess.
You're experiencing the cognitive dissonance from trying to understand unnecessary complexity.
As I mentioned, complexity abounds everywhere. It is hard to say what is unnecessary and what is not.
I get where you're coming from and while I still fucking loathe the term "isomorphic" in my web development, I feel that an application of basic engineering practices go a long way in development of even modestly-complex websites.

I used to get worked up about front end devs calling themselves "engineers" but after 12 years in the trade, I think my dream team of web devs consists of more industrial engineers than CS grads.

I understand all of these words and they are important to my daily workflow because I do serious frontend development. I'm not trying to offend you but your comment simply indicates that you do no serious frontend development.
For anyone struggling with the complexity and boilerplate of Redux, at my day job we added Redux Toolkit (the newly recommended add-on by the Redux project itself!) and it solves most of the verbosity issues! We use it to share all our state logic over mobile and web and it works excellently now. I'd actually recommend Redux w/ Redux-Toolkit, for the first time ever.
There's a library called rematch that's been implementing most of these ideas for a long time, but sadly it didn't get popular enough and it seems to be somewhat lacking maintenance. It was such a pleasure to use (in comparison) that I just can't believe how people ever work with those massive switch statements.
One of the reasons we created Redux Toolkit was to eliminate the need to write those switch statements :) (also action type strings, action creators, object spreads, having to split logic for one feature across multiple files, and all the other "boilerplate" concerns that users rightfully complained about over the years.)

- https://redux.js.org/tutorials/fundamentals/part-8-modern-re...

- https://redux.js.org/tutorials/essentials/part-2-app-structu...

- https://blog.isquaredsoftware.com/2022/06/presentations-mode...

Weird that XState was not mentioned.
Agree. I am working on a side-project and using XState for local state management, and Jotai (with Immer) to send that state to various components in my app. It is working really well and mitigating unnecessary rerenders.

More moving parts than I might like, but they play together nicely and I haven't hit a single wall yet.

For me using xstate was a game changer. Pulling logic out into a state machine gives you so much clarity over your application logic. Also nice to have your business logic defined with something that is framework agnostic.
Yes! An explicit state machine is my desire for the UI development since the Delphi days of 1990s. It makes things so much more observable and sane.

Redux nudges you to build that state machine by hand, in the form of the reducers folder, around the centralized state. While elucidating, it's still a lot of boilerplate (which you can sort of factor out), and it's still not one clearly laid out entity.

Fun fact: one of the XState devs did a proof-of-concept showing how to use XState state machines as Redux reducers and integrate the side effects handling as a middleware:

https://github.com/mattpocock/redux-xstate-poc

We'd like to work together to turn that into a more official integration sometime soon.

This looks excellent. I see a lot of potential in the idea of combining the strengths of RTK(+Query) and XState. Thank you for sharing!
Yeah, if you've got any particular use cases or ideas for how you'd like to use them, please put up a discussion thread in the RTK repo and let's talk!
Once you see the light of state machine based UI's, it's immediately obvious and you never want to go back. The problem comes in convincing others to deal with the boilerplate. It's worth it, but it never really sinks in until you use it.
I've had this same experience as well! Wrapping your head around the concepts at first can be a little tedious but it makes application logic and behavior far more predictable. The ability to visualize them is the icing on the cake as well and is a nice extra tool that eliminates the need for arrows and boxes in a sketchbook or a Google Drawing.
I'm just tired of the redundant reinvention of new "terms" for literally everything in JS. I don't want to know what an "atom" or a "proxy" or a "thunk" is. These are meaningless abstractions that simplify down to a store and a callback. Stop inventing terms to make yourself feel smart.
FWIW, the term "thunk" is a long-standing CS term that long predates Redux [0] [1] [2].

In fact, my first job back in 2008 involved a C++-based emulator/VM framework, and the devs used "thunk" to refer to jumping from the original program binary out to altered/replacement code written as C++ to add additional behavior or replace functionality.

"Proxy" is also a long-standing term as well that describes wrapping or replacing functionality of a system, which is why it's used for HTTP servers and why it got used for a new JS capability in the ES2015 language spec.

[0] https://en.wikipedia.org/wiki/Thunk

[1] https://stackoverflow.com/questions/2641489/what-is-a-thunk

[2] https://devblogs.microsoft.com/oldnewthing/20081020-00/?p=20...

You are literally complaining about Computer Science
(comment deleted)
Curious layman here (no web dev experience). I thought the main selling point of reactive programming was that it abstracted away the need to manage state. What problems, then, does a state management library solve in a reactive framework?
Reactive programming provides the capability to react to changes in state. You still need to hold that state somewhere, mutate the state from your reactive code, and control the lifetime/scope of that state. Managing state becomes more of a concern with reactive programming, but it also becomes very explicit; this is something that traditional MVC approaches try to hide, to the detriment of anyone trying to understand the data path of your application.
ELI5 why Excel can update all of the sheets from all the different data sources without overloading its user with “state management” thing and React can not?
This talk from Rich Harris should answer most of your Qs:

Rethinking reactivity

https://www.youtube.com/watch?v=AdNJ3fydeao

He goes over the history of spreadsheets, reactive programming, and dunks on react.

(comment deleted)
I was negative in my deleted comment, but watched the video anyway (I remember skimming through it back in 2019).

It answers my question only tangentially, because it doesn’t touch state management per se. The key point was, what’s the fuss about React SM and why it must be explicit in it. The video basically says that it is a nonsense which may and should be avoided. I agree, and so still don’t get what ggp is talking about.

Most state management libraries help you deal with the problem of sharing state between multiple components. In modern React, most components are just functions, so this boils down to passing state through deep call stacks.

If you have a component A, which renders component B, which in turn renders component C, without a state management library you'd have to pass state from A to B to C (this is often called prop drilling). The longer the call stack, the more irritating this becomes.

State management libraries allow component C and component A to modify, and subscribe to a shared state store. Redux et al. are effectively the reactive version of global variables.

React already has a mechanism for this (Context). It's not the primary reason to use a state management library.

Any discussion that doesn't revolve around a difference between the two is akin to suggesting re-implementing a standard library call for literally no reason.

The commenter I was replying to wasn't inquiring about why people choose third-party state management libraries in lieu of the standard context api. In fact, he's not even a web developer.

He seemed to be wondering why you need a state management solution in the first place. If he had asked why people prefer Zustand, or Redux, or Recoil over React's built in context api then I would have replied accordingly.

I was largely expounding upon the linked article's first list item under the heading "The problems global state management libraries need to solve". The first item in that list begins with:

> Ability to read stored state from anywhere in the component tree. This is the most basic function of a state management library. It allows developers to persist their state in memory, and avoid the issues prop drilling has at scale.

I would argue that the primary function of state management libraries is still sharing state between components. Third-party libraries might expose a more performant, simpler, or more intuitive api, but that's still their primary purpose. Analogously, if a non-compiler developer asked why programming languages need memory management, you wouldn't delve into the differences between reference counting and tracing garbage collection.

As we know from computer science, there are two difficult problems: naming things and cache invalidation. State management is inherently difficult in web apps (I think it's inherently difficult for any complex application). They are effectively distributed systems with local caches and remote server data that need to be synced correctly in order to manage a user application at scale. That all has to align with how the presentation logic is built. It has to deal with the constraints of the web platform, etc.
For anyone who has not had the pleasure of working with these simpler “atom-based” state management libraries, I would implore you to try Jotai (https://jotai.org/), which is mentioned in the article.

Jotai’s atomic model and ease of use has made writing complex React applications far more joyful for me.

Has React become too big for people to not consider other alternatives? I used to be a big React advocate but realised that it is getting too bloated with every new release. Have been looking into Svelte and it is turning out to be a breath of fresh air compared to whatever React is culminating to be.
I think it’s exactly the opposite as you describe. The React API has been incredibly stable and consistent for years now.
I've advocated for Redux in many commercial projects because I knew that it worked well at scale. There was occasional pushback due to the large amount of boilerplate code involved (actions, reducers, sagas, models/interfaces if using Typescript) but the team mostly settled on Redux because it was the best supported and most widely used state management framework at the time. IMO the larger pool of devs that understand a framework, the better a project's chances are of success mainly because the industry has a high turnover of developers and it's easier to find replacements to maintain the existing code.

That being said, I'm now a huge fan of react-query.

Question for you since you seem knowledgeable about React state management. When I did a lot of back-end programming in Java we frequently coded to interfaces where we could swap out back-end implementations for ORM for example if needed. There's an ongoing joke where nobody actually did that.

Is there something like that in React? My understanding is that there's a lot of coming and going of state management frameworks, and it would seem to me committing to thr API methods of a particular framework would be risky. Or am I thinking at the wrong abstraction level?

To some extent, this was the purpose of the "container/presentational" pattern that was popular for a few years [0]. By splitting your components into "containers", which were responsible for managing data fetching, and "presentational" components, which just received data as props and displayed the UI, in theory you could swap out the state layer someday without having to alter most of the display handling.

But, the community really began over-obsessing about that, and often treated it as a rule you _had_ to follow (to the point of people seeming to panic and asking for help about whether a particular component should live in a `/containers` folder, `/components`, or somewhere else).

Dan Abramov, who wrote the article that helped really popularize that approach, later updated it to say he no longer finds it very useful.

In addition, React hooks push you towards a very different approach, where each component is now responsible for calling the hooks that it relies on for data fetching. That hook may still abstract where the data actually comes from, but the calls are now part of the component itself. I talked about this change in approach in a blog post and conference talk conference talk [1] [2].

Finally, the testing approaches in the ecosystem have changed as well. Instead of "shallow rendering" components using the Enzyme library, the community has moved on towards more "integration"-style tests with React Testing Library. This does require more setup work in tests to ensure you have all the various data providers wrapping the components under tests, and real or mock data being loaded, but the tests themselves become simpler and any state library usage becomes basically irrelevant to the actual test implementation. See [3] and [4] for some thoughts on that.

Soooo... yes, you _can_ write more abstraction layers, split your components by "containers", and even add DI via React context or some other purpose-built library if you want to. You could even abstract out all the UI components you use from a particular library just in case you end up swapping date pickers or something. But as always, it's a question of whether that will actually provide a benefit, now or in the future. And in general, most React apps do not bother with those extra abstractions.

[0] https://medium.com/@dan_abramov/smart-and-dumb-components-7c...

[1] https://blog.isquaredsoftware.com/2019/07/blogged-answers-th...

[2] https://blog.isquaredsoftware.com/2019/09/presentation-hooks...

[3] https://kentcdodds.com/blog/testing-implementation-details

[4] https://blog.isquaredsoftware.com/2021/06/the-evolution-of-r...

(comment deleted)
Redux is weird because it's only necessary at a certain scale, but it's the same scale where having a global shared object makes things really confusing. For example, suppose you're trying to deprecate a reducer; you need to find all consumers of the state it manages. But you can't, because there's no actual dependency between a reader of the store and the reducers. You're stuck wading through all the useSelector or connect() calls in the entire app; hopefully the reducer was managing a state branch with an easily-greppable name.
(comment deleted)
I've been using Pullstate for medium-sized apps for a couple years and it's fantastic: uses Immer under the hood (so it's all immutable even if it look as it wasn't), it's API and mental model are vulgarly simple, and my impression is that it's quite performant. I also like that the store is disentangled from the component tree, which allows for plenty of flexibility. Totally recommended!

https://lostpebble.github.io/pullstate/

Using pullstate in production for a number of apps now; seconding this recommendation. Almost all of the complexity / mental overhead / boilerplate mentioned in this thread mostly goes away with pullstate.
I'd like to share my approach to React state management, because after a decent amount of industry experience I've stumbled onto a solution I find to be very excellent.

Firstly, I use the open source library Pullstate[1], which I find to be as effective as any alternative but _far, far_ simpler to understand and use.

All my components are functional components. For any state that _only_ that component needs, of course I simply use the useState hook.

When things need to be shared among multiple components, I create a Pullstate store - for instance, for an app I'm working on now I have a UiStateStore like so:

    type UiStateStore = {
      isSidebarOpen: boolean;
      // etc
    };

    export const uiStateStore = new Store<UiStateStore>({
      isSidebarOpen: false,
    });

Then, in the components that need to know if the sidebar is open, I import this store and use its state hook _the exact same as the normal useState hook_, like so:

    const isSidebarOpen = uiStateStore.useState(s => s.isSidebarOpen);
You simply pick which properties you need off the state object ("s"). When you open the sidebar, you simply update the store like so:

    onClick={() => { uiStateStore.update(s => { s.isSidebarOpen = true; }) }}
And automatically, any components using uiStateStore.useState and watching the isSidebarOpen property will get updated, exactly the same as the normal useState hook - just shared.

It's so dead simple and has made complex app-building so much easier for me.

By comparison, Redux in my experience has a harder learning curve with a lot of unnecessary pieces and boilerplate - it seems crazy to me that people think it's a great solution and it feels like people have convinced themselves all those pieces are "necessary", where in my experience they are anything but.

The one caveat is that if I have a component with many handlers, e.g. onClick, onMouseMove, onContextMenu, onMouseLeave, etc (and in some cases I do), components can get bloated. I haven't found a fix to that yet. But that's more an inherent issue with react than anything to do with state management.

[1] https://github.com/lostpebble/pullstate

You might like the atomic state management libraries mentioned in the article, they simplify things to another level because they sort of remove the concept of a store entirely. For example in Recoil I would do this:

    export const isSidebarOpenAtom = atom({key: 'isSidebarOpen', default: false});
And in the component: (it mimics useState)

    const [isSidebarOpen, setIsSidebarOpen] = useRecoilState(isSidebarOpenAtom);
and later...

    setIsSidebarOpen(true)
Recoil will then re-render any component that relies on isSidebarOpen.

The only real boilerplate is having to specify keys for each atom, which I find a small price to pay for such simple global state.