The new wave of React state management
frontendmastery.com
frontendmastery.com
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.
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.
- 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.
Also, thanks for your work -- really, really appreciate 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.
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.
What happened to the whole "service" pattern? Isn't this the core problem it solved for Java in the 90's?
* 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 ?
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.
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.
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.
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.https://blog.isquaredsoftware.com/2021/01/context-redux-diff...
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.
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 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.
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 like its query and request primitives, but I don't like that they are coupled to the cache.
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?
Frontend shouldn't be complicated and we've been lied to, you are not Facebook, you don't have billions to throw at engineering, you must pick the path of list resistance
And hooks. Sorry but React started to go crazy when hooks IMHO.
You know, with how hooks work in Vue, things seem be easier to understand and get started with.
Though at least with React I've had plenty of projects end up with render loops that are hard to debug because of how everything is written. So much so, that I wrote a blog post about it a while back "Modern React is broken": https://blog.kronis.dev/everything%20is%20broken/modern-reac...
Personally I wish that we could already have some tooling that'd tell you something along the lines of:
Render loop detected! The following chain of calls was responsible for it: A -> B -> C -> A -> B -> ...
Please check the useEffect hook on line X, which has the following items in its dependency array which changed: [Y, Z]And lifecycle methods are just as "magical" as hooks. If you just thought about why the "rules of hooks" exist for a moment instead of just hating change for the sake of it existing, you could probably intuit how they work under the hood.
Lastly, lifecycle methods don't allow you to co-locate feature-related code, can actually create more bugs (if you have logic in `componentDidMount` but forget to add something similar to `componentDidUpdate` for example), and any logic contained within lifecycle methods isn't composable / reusable.
I really don't understand where you're coming from at all with this.
I recently did the same, and hand's down, the vue ecosystem is weak.
Vue is difficult to work with (unable to render content without a functional component wrapper, poor support for style libraries like tailwind), and the state management ecosystem is fractured between vuex and pinia. Worse, much of the help online is for old versions (vue2). Storybook 'out of the box' is broken and doesn't work at all (unable to resolve '@/...' imports).
It's been painful.
> Everything is intuitive and I am not googling days to fix tooling issues
That has not been my experience; I spent literally 3 days customizing my storybook install to make it work, and digging through 'try this...' threads on https://github.com/storybookjs/storybook/issues/11989
Can't relate. Tailwind works fine with anything that supports PostCSS. I run it with Vite and there's zero issues.
> the state management ecosystem is fractured between vuex and pinia
This is also just not true. Pinia is officially replacing Vuex as the recommended store library for Vue [1]. They're also vastly similar in how they do things, so the knowledge transfer over from Vuex to Pinia. And Pinia just address most of the design goals mentioned in the article in the most simple way.
As for Vue 2 -> 3 transition, lots of the larger UI frameworks in the ecosystem is struggling to migrate, despite lots of efforts on the compat layer to smooth the transition, which is a bummer. But as long as you're not doing those sophisticated things, Vue 2 examples should work out-of-box on Vue 3 as well. There are surely less resources for the composition API, but the official introduction guide has been good enough in my experience.
I actually recently looked into most of the frameworks out there and their migration efforts.
So far, I only found three viable options for Vue 3:
- PrimeVue https://www.primefaces.org/primevue/
- Quasar https://quasar.dev/
- Element Plus https://element-plus.org/en-US/
We went with PrimeVue and while using PrimeFaces was an incredible pain with Java, the Vue version seems a bit better. Then again, it's kind of odd that libraries as popular as Bootstrap don't have complete bindings in the form of Vue 3 components.It's my preferred go-to for Vue and React. Also opens the interesting possibility of supporting React and Vue components off the same stores..
What do you mean fractured? They official recommend Pinia: https://vuejs.org/guide/scaling-up/state-management.html#pin...
> Storybook 'out of the box' is broken and doesn't work at all (unable to resolve '@/...' imports). It's been painful.
So you're blaming Vue because of bad experience with storybook?
Follow all the steps.
$ jest
Error: Cannot find module 'vue-template-compiler'*
test-utils is a first party package.My experience is the same across the ecosystem; 3rd party packages, 1st party packages. My 'Out of the box' experience is:
It doesn't work unless you screw around and manually patch things.
There's good things too, but this comment:
> I am not googling days to fix tooling issues
Resonates with my experience at 0%. I don't care who's fault it is, the tooling experience has been bad.
(* no, I really did follow all the steps -> https://stackoverflow.com/questions/65790642/test-suite-fail..., it's just broken)
My only problem is that my IDE of choice (WebStorm) is incapable of providing any sort of autocomplete for it, with JavaScript. For example, consider the following store, as an example from their documentation:
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
});
Really simple to define it and then change it with $patch, right? Well, when I try to access the field which is included in the default values, my IDE won't show me the available fields when used in some component: const counterStore = useCounterStore();
const isSomethingExceeded = counterStore.count; // .count does not get offered as an autocomplete option
That is kind of annoying, when you have a whole bunch of different fields in there, even if you can predict which kinds.Well, there's also the thing where WebStorm refuses to insert <script setup> tag or even add imports inside of it if it's present in the header of the page, but instead I get a redundant <script> tag at the bottom with the old Vue syntax for defining components, which I then have to clean up manually.
But I don't want that. I want to build interfaces and not "manually shift" my local state management.
I think the shape of solution needed for these two problems are quite different.
Meanwhile, most developers don't need to have customized actions sequence. Therefore they can just pick one state management for them to delegate these stuffs.
That's not the tools' fault.
100% this. Though devs in one might not move to the other in short period. So this knowledge should come from outside of their work, which I don't find many in the net.
The other reason is a lack of exposure to how other technologies for GUIs have handled state for decades.
Maybe look outside the react bubble and see how many of these "issues" just disappear when you stop acting like react is some fundamental particle of the web.
It saddens me that so much of research in developing better state management techniques is in such a bloated and dependency-laden environment as JavaScript on the web. I like QML's reactivity system, but its evaluation engine is JS-based, dynamically-typed, and dynamically-scoped, and the UI engine itself is a buggy mess. And GTK4's list APIs promise to be better than the clusterfuck of Qt Widgets/Quick's QAbstractItem{Model/View} system (which abstracts poorly over list/column/tree collections, and widget-internal, cross-widget, and cross-application drag-and-drop), but I haven't tried that either.
But on a complex app, to see "dirty bitflags", "intermediate values", "redrew the UI in the middle of mutating document state", I feel what you feel, and I want to share what helps me and my team overcome this kind of complexity.
This happened a few years ago to my team. We alleviated this with several solutions. The most effective solutions is to 1.) separate UI logic/compositive component from UI visual/appearance component, 2.) separate UI component from actors.
1.) Separating logic from visual helps developers define problems separately, especially those around the required states, step-functions, and lifecycle. We made this separation because of three things: 1.) We observed that our programmers are simply divided into these two categories, 2.) The dependencies of these two sets of components are simply different (e.g. logic --depends-on-> actors and ownership/lifetime management modules, while visuals depends on DOM, rendering, setting up callbacks), 3.) It is actually good to have a high cohesion between UI and logic modules. You may want to use the same UI for a slightly different logic, and vice versa.
2.) Separating UI component from actors helps a lot with decoupling state changes and re-rendering, and actually gives developer a chance to separate/abstract/generalize concerns when things have gotten too complex to be written in a UI component.
Actors is what I call any kinds of data that can act, have their own agency. It can be what you call a service, worker. It can have a Timer-based or queue-based internal lifecycle-management that does not require interaction from user (very useful for things like updating background data at interval)
UI component can both "own" or "borrow" actors. "owned" actors dies when its parent component is destroyed, while "borrowed" actors does not die when its borrowing component is destroyed.
We decouple actors from UI component and set up bridge between them. UI component can signal actors by function call, and actors can get back to the UI component by using either a return value or event/callback system.
And the last but not least, writing app in a smartly typed language combined with functional domain modelling helps a lot (see about it here https://www.youtube.com/watch?v=Up7LcbGZFuo).
Before we got into where state should be and when is UI reload/rerender triggered. I'm going to tell you a little imaginary problem.
Imagine you are building an application for downloading several huge files.
1.) This app must have a download page to trigger the downloading of various files as well as monitor their statuses.
2.) a system to manage multiple downloads. It can queue multiple downloads but only one should run at a time. But, regardless of the download page is open or not, the downloader should run its download.
3.) a persistent notification system for the app to notify the user (e.g. if a download succeeded or failed). This notification should be persisting, meaning that if the user does not dismiss a notification, it will stay there. The UI for the notification system looks like a smart phone's one.
4.) There are several other pages in the app.
Before we got into asking where state should be placed, we should examine what actors are there. There are the "downloader", the "download page", the "notification system", the "notification UI".
Because we have these several actors, we need to assume that all of these have several local states. All of these actors need to be in the component tree, but not necessarily bound to the rendering lifecycle.
The dependency arrows look like this:
- "download page"=>"downloader"=>"notification system" - "notification UI" => "notification system"
These dependency arrows form the component tree automatically. Now, let's examine the solution:
- The state that tracks the notification should be in the notification system. The user-facing message in a notification item should be a copy that is put into the memory of the notification system.
- The state that tracks the queue of multiple downloads and the function that schedule the downloads should live in the downloader. These scheduler will have its own timer-like mechanism to notify itself that it needs to run other task if one is finished.
- The download page "listens" to events and "borrows" data from the "downloader". So if the downloader makes a change, the downloader page will change too.
- Last, the notification UI. The notification UI lives longer than the download page, because the user can switch between page but the notification UI stays on. The notification UI listens to the notification system for changes in its state and borrows its state.
- If you pay attention to the dependency arrow, notification UI and and download page both are the descendant of the notification system, but only notification UI react to "change-signal" from the notification system. Download page should not react to any signal from notification UI (e.g. no rerender).
- This is an indicator that the notification system must not be bound into the re-render lifecycle of the tree of components. Notification UI should explicitly subscribe to the notification system in order to allow other page to ignore the notification system. In JavaScript/TypeScript it is pretty easy to implement a callback-based event emitter that can be attached/detached.
- The notification system and the downloader is what I call an actor that can, but not always, be bound into the render lifecycle. It lives with the component that spawns and destroy it, but is detached from it.
This "thing" is partially influenced by actor pattern. The other influences are entity component system and Alan Kay's original of OOP.
I found that state management solutions are always incomplete for the problems we have in our team.
When you say "stop acting like react", it is actually true that some mechanism needs to escape from React's render loop while retaining its tree-like structure. New members to the team will have to be introduced to the idea that an actor (object that acts on data) does not need to be the same entity as the React component itself. We end up taking a lot of concepts from other domains such as game engine, Rust's ownership/borrowing
What we ultimately need are that's not fulfilled by many state management libraries out there:
- Instead of global vs local, we need management of scope, referability, and intuitive dependency injection. - Instead of state, we need management of actors. - Management of lifetime, ownership, and borrowing (concept taken from Rust) - A differentiator between actor, function, and data (which entity should or should not do stuffs) - Two-way communication channel - A consistent code semantic that makes sense to describe it all because TypeScript/JavaScript does not cut it.
If the logged-in user is this or that and/or the component state is this or that then do this unless that, and if this part of the component changes/re-renders to be that, then refigure the entire state all over again so this or that can be adjusted if necessary...
I mean, I get it, basically any GUI is going to have matters like this. And I get that the idea is to work really hard to limit the contract of each sub-component so that it can feel pure and only be dependent on a limited number of parameters/properties coming in. After all, on the backend it's also easy for a bad programmer to write a function/method that accepts forty parameters and has horrendously complex logic inside.
But in practice, something about the product cycle and the feature cycle of React just makes it so easy to turn into a huge snarl really fast. I feel like I'm battling against the tide. Maybe it's just something specific to our team and I need everyone to just freeze until we get some better practices in place.
There are things that are "global state", at least in the sense that you're likely to care about them in a hundred disparate places. If you're writing a UI, odds are at some point you're going to want to see "current user ID", "language selection", etc.
Why can't we just say "it's global" rather than doing all sorts of plumbing to pipe these details around the code base? I know we have stuff like contexts, but it feels a strange workaround, like someone saying "I'm on a diet, no sugar... but put 12 teaspoons of honey in my tea."
I know that the standard line is that it's difficult to test globals, but maybe developing a better test mechanism is a more tractable problem than having to add extra infrastructure to reinvent globals.
It's sort of a logical fallacy. How could global state be bad if reading from a database o an API is essentially that? The problem is mutation, that if global state is mutated somewhere, other places that read the same state don't know about it.
Modern state managment instead is immutable and is not even an original idea of React. Redux was basically copied from Elm's state management.
The simplest and most obvious pattern I've ever used was essentially an angularjs (1.x) directive that allowed templates to grab named references to services and directly reference that state. Mutations were also exposed on the services as plain old functions, and it all just kind of worked. Looking at a template, it was blatantly obvious where the data and methods came from. The two way binding just made it dead simple to grok what was going on once all the layers of abstraction (controllers in the AngularJS world) that might rename things or make up new clever abstractions were gone.
I've built with React a half dozen times now, and I still haven't found anything that got me close to that level of simplicity. I'd say most of my experience has been that it's at least 10x more complex with all the gymnastics you have to do to keep the data flow unidirectional.
[1] https://mithril-by-examples.js.org/examples/tweeter-box/
Maybe the backend of a crud app is fundamentally simpler than a user-facing UI.
For example: most backends will let you query arbitrary data, but they won’t alert you when that query changes.
So we solve this on the frontend (often laboriously, with spaghetti).
IMO backend developers are often unwilling to do anything beyond CRUD… and specifically they are often unwilling to model the dynamics of a domain. They want to treat the domain as frozen in a moment of time.
In general, I would love to see more backend developers be more ambitious about trying to see which jobs the frontend is doing that could be solved on the backend. I think that could distribute some of the workload more evenly, and give BE devs a taste of what that higher level of complexity looks like.
Sometimes I think backend developers just write off hard problems that they could solve because “that’s a frontend concern”. It’s an easy excuse.
For logged in stuff in particular, it's easier IMO to fork entire pages/components completely instead of trying to make things handle both cases. You can always come back and refactor if it turns out the splitting was unnecessary, but it's really hard to go the other way.
I think Recoil is supposed to fix this issue but I haven’t learned it yet.
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.
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.
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).
So there are some solutions available if normal mobx is not enough.
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.
That being said, I'm now a huge fan of react-query.
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?
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...
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.
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.).
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.
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.
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.
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.
And that's why you use a client-side cache.
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.)
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.
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?
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.(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)
It doesn't. How often do you have the same information redundantly displayed in multiple places, on the same screen?
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
- 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...
This is for pure client side stuff, of course.
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...
I’ll poke again but last time I tried I couldn’t avoid Typesafe Actions library.
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...
[0] https://blog.isquaredsoftware.com/2021/01/context-redux-diff...
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.
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...
By the way, thanks for your hard work on Redux, appreciate it.
I mean, sure but the DOM is not even a factor compared to the actual WebGL rendering going on. The DOM is only used for UI, menus and things like that. So the bottleneck would still not be React or how I handle state.
With that said, do you know any libraries like that?
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.
https://github.com/mattpocock/redux-xstate-poc
We'd like to work together to turn that into a more official integration sometime soon.
SSR is just one technique.
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.)
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.
https://github.com/vercel/next.js/tree/canary/examples/with-...
And Jotai: https://github.com/vercel/next.js/tree/canary/examples/with-...
Rethinking reactivity
https://www.youtube.com/watch?v=AdNJ3fydeao
He goes over the history of spreadsheets, reactive programming, and dunks on react.
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.
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.
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.
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.
Then we can party like it's 1999!
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.
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.
Of course it’s best to avoid UI programming entirely, but in many domains it’s necessary.
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?
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.
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.
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.
Something is not working
Not to mention, as an example touch became a widespread new UI paradigm in the last 10-20 years.
User interfaces are complex, poorly specified, and subject to rapid and often capricious changes in the middle of development. Don't blame the tools.
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.
Such is the hype cycle of frontend development.
It is universally worth your time to read it if you haven’t :)
I haven't read it, but wish I had seen it before struggling through the original
But with the translation, I just devoured and appreciated it.
For bigger projects, custom middleware is where the magic is. If you don't understand redux well enough to write your own middleware (it's not very complex, it's just under documented for some reason) then it's better to use one of the other, simpler abstractions instead of adding one abstraction (redux) then piling on more abstractions to make it usable.
- https://redux.js.org/introduction/why-rtk-is-redux-today
- https://blog.isquaredsoftware.com/2022/06/presentations-mode...
Also, note that RTK has a new "listener" middleware that simplifies the process of "run this code when some action is dispatched". You can certainly still write a completely custom middleware if you _want_ to, but the listener middleware handles that work for you.
Since we last talked I have worked on projects where rtk was used because the devs didn't really grok redux and so far that's the best use case I've run across. That said, now when I run across situations like that I tend to guide clients to simpler state management solutions instead of piling on another layer of abstraction.
It's possible to do the same thing with a react component + useEffect, but I thought a pure-redux solution would better fit the spirit of Redux.
When you add a listener entry, there are four options to specify how the middleware knows when to run that listener: 1) `type`: an action type string; 2) `actionCreator`: an RTK action creator function like `todoAdded`; 3) `matcher`: an `(action: AnyAction): action is MyAction => boolean` type guard; and 4) `predicate`: an `(action, currState, prevState) => boolean` function.
In all cases, the middleware loops over all entries every time an action is dispatched, and checks to see if any entry is interested based on those comparisons. For the first three, it's just "does this action type match".
_But_ the `predicate` callback receives `currState` and `prevState` as arguments. That means that you can write checks to see if "this state field has changed due to the action":
return currState.counter.value !== prevState.counter.value
or if "this state field matches some condition, such as: return currState.counter.value >= 3
This is covered in the API docs:https://redux-toolkit.js.org/api/createListenerMiddleware#st...
FWIW, skimming the repo you linked (which I assume is your package), I'm pretty sure RTK either does the same things already (creating actions and reducers) or has equivalent solutions (listeners for "selector effects", `createAsyncThunk` for the "async middleware", RTK Query for data fetching).
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.
But you're right. I haven't done much front-end at large scales. Only at one employer for a short while.
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.
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.
That being said, prop drilling was made more of an issue than it really is, especially considering the boilerplate needed for state management libraries like Redux.
But if there does need to be a global store, I usually reach for zustand as the API is probably the easiest out of the ones mentioned.
Jotai’s atomic model and ease of use has made writing complex React applications far more joyful for me.
But that's just me...
As I've talked about in a couple posts and presentations [0] [1], there's a distinction between "inherent" and "incidental" complexity. Dispatching actions and using reducers is "inherent" - it's part of Redux, and you can't remove that because then it's not Redux. But things like writing `const ADD_TODO = "ADD_TODO"`, object spreads, etc, aren't _necessary_ for using Redux - they're inherent complexity.
Which is why we wrote Redux Toolkit to fix all that incidental complexity :)
Redux still won't ever be as few lines of code as various other libs, but writing Redux code today is much simpler than it was previously... and yes, the _benefits_ of Redux (predictable code, debugging, separation of state updates, middleware) are still entirely valuable.
[0] https://blog.isquaredsoftware.com/2022/06/presentations-mode...
[1] https://blog.isquaredsoftware.com/2019/10/redux-toolkit-1.0/
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.
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).
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.
> 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.
Can anyone parse that sentence please? Was this article written by GPT-3?
Is it the lack of platform API support? Is it the community trying to make everyone a library developer? What's wrong with frontend?
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.
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.
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...
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.
You can do all of that pretty easily with a vanilla redux.
> 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.
- 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...
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.
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>.
"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.
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
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.
More moving parts than I might like, but they play together nicely and I haven't hit a single wall yet.
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...
I find it strange this is still such a hard problem to solve in an environment with a slew of options for key value global, local, and remote storage
One thing I learnt from all these JS libraries and Node is that nothing is for free. If it seems too easy, then something gonna bite you in the ass down the line.
However I find it very difficult to read: there's so much text and not many example to illustrate.
Then the outcome is that about 50% of the beauty of reactive development is lost because the whole point was that your UI representation became isomorphic with your state. In doing that a whole class of bugs went away. Then we inject state management and suddenly all those bugs are back again, living in layers and layers of boilerplate code generated to satisfy the patterns required by the state management code.
tldr; the whole reason I started using a reactive UI framework was to eliminate complexity. When you bring it back in any form then you destroyed the main value proposition.
When the <UserLoginModal /> finishes its login process and sends a bunch of data about the newly logged-in user to the global store, I'd like for some N other components in the tree to consume that new information. State management solutions help relay that new information down the tree to the components that need it.
Local storage and session storage can persist data, but have no way of informing components that something has changed without implementing a setState() and listener/callback system, at which point you've recreated yet another state management system.