I assume the idea is for changes in the data to trigger changes in the UI? If you just keep the raw data on an object you still need an additional mechanism to synchronize the data on the screen.
You could even use document.querySelectorAll() to go through all the elements on your page, and update them based off values in the state, whenever window.setState() is called. This is basically all front-end frameworks in a nutshell. You've invented reactive programming in the browser.
Perhaps my way of thinking is React-centric but this feels like a pretty inefficient way to do it. Feels a bit like reinventing the wheel poorly when things like MobX exist, sure it’s simple to start off but then you hit performance issues or issues with dependent state etc. etc. and you realise why, for all the derision they sometimes get, libraries exist to solve this problem and they are useful.
But for a simple thing built with JS that isn’t going to need to scale to a large codebase or whatever, sure, keep it simple!
I build games where I run a global setState on the root component 60 times a second and even with deeply nested components it's not even a factor in performance.
If you don’t have the overhead of React you have multiple orders of magnitude more headroom. That’s enough to push common UI idioms as far as they can go in many cases - for example, interactive tables with at least 10^5 rows perform well in modern browsers so using the DOM means you can keep things simple and fast without having to struggle with the scrolling, text selection, accessibility, etc. concerns that custom widgets entail. An awful lot of applications exist where you need more than hundreds of records but fewer than millions.
Interesting, I wonder if the overhead of updating UI based on state “efficiently” (eg MobX style tracking of what should re-render when certain data changes) would actually be slower than brute force updating everything in this scenario? A bit like I’ve heard people talking about just rendering the UI to a canvas, a whole new render 60 times a second, is actually the most efficient way to update a UI for certain cases like really big stock ticker displays (versus selectively re rendering bits of HTML)
For anything non-trivial you'd have to add some kind of listener or tag to every node, find said node, and then update (ideally in a performative way). Said updates cannot destroy existing state, but must patch it. Updates must also not break existing attached event listeners. The list goes on and on.
Things are faster than you’d expect. Tables with millions of items don’t work in any framework, but tables with a hundred items work in all of them. Since no one has really cracked the nut on performant UIs with lots of things going on, anything homegrown will be simpler and performant enough compared to the major competition.
That is one way to do it, albeit a seemingly naive way. I can’t imagine the entire page is linearly searched to update a UI element these days. Perhaps an entire component in some frameworks?
I don’t know and can’t easily find out the time complexity of things like React setState(), but would hope that for frontend code bases where there is a build step(s) and/or for typed frontend languages we could achieve better than linear (especially if N is every element on the page)
A lot of state either never or rarely changes, so one must wonder if you really need a lot of complexity to manage all of that.
Some state does change a lot, and for that: sure. But if you just have currently logged in user or some such (which is quite often the case): meh, it just seems like a complex way to set a global variable.
This week I spent some time removing the Vuex state management in favour of window globals and it made everything a lot simpler and the bundle size a lot smaller (the other option would be to migrate to Pinia, which is significantly smaller than Vuex, but then I'd probably have to update to the next incompatible version in 2 years, and this seemed more robust).
Storing state in plain objects is a nice way to go when you pick a framework that doesn't care about state management.
Forgo, Mithril, and Crank all require explicit commands to rerender, unlike React and friends, which need visibility into everything that could ever trigger a rerender so they can handle it automatically.
You'd think manual rerenders would be a big pain, but I find it a small price for allowing by state to just be regular ol' variables. It makes everything feel simpler, like I'm just using the language instead of expressing my business logic through the framework.
Mithril does full rerenders by default on event handlers and upon requests made with its request util. Normally, this would be a no-no in React land, but Mithril is fast enough that it's a non-issue, and it greatly simplifies state management (POJOs work great for global state, and closures enable a simple way to make stateful components).
It was always MobX for me, I’ve built a few sites and React Native apps with it and found it extremely productive in exchange for some magic, but it could be seen as somewhat heavyweight compared to some more “modern” solutions. Jotai and Zustand are two that I want to investigate but not used them yet.
My personal favorite is re-frame for ClojureScript. I have used it to build things like a LaTeX editor with live preview, electron app frontends, and web-apps. There are wrappers like Kee-frame that extend re-frame by linking the URLs query to the current state, making it possible for users to easily share URLs to different states of a single page app.
The cons are:
- A fair amount of work to make changes to the state side-effect free, but the documentation has a good tutorial on how to approach this.
- It's for ClojureScript, so it's a non-starter for organizations that will not consider anything outside of the mainstream.
I have also used Elm. If I recall correctly, Elm's state management paradigm was inspired by re-frame. I found writing JSON decoders in Elm to be demoralizing and tedious. Since the creator of the language was vehemently opposed to providing any real JSON support I decided to move to ClojureScript.
Thus far I’ve really enjoyed https://xstate.js.org/. It’s fantastic for ensuring that your application is in an expected state and to control transitions between states. When it’s overkill you can often just drop to useState for simple stuff.
One cool thing about Reagent (forgot where I found the quote): Reagent benchmarks showed it was faster than vanilla React due to how Clojure uses immutable data structures. The React team used it as inspiration for a big refactor (not sure if that's in it already, think the quote I found was from ~2017-18)
React specific but i’ve built a couple non-trivial applications with Recoil. I enjoy the ergonomics of it and building up a state graph. At its simplest it like a global version of useState which is really all you need, something you’re used to but happens to be shared. I prefer things that have simple primitives that can be composed into something complex. The documentation is good for describing what it does but not the best at easing you into it’s mental model.
Principal eng working with 3 teams on a ~500k loc React site here. After lots of experimentation, we’re just using React contexts.
- Redux is cool but there’s so much decoupling that it gets hard to read the code. New hires would take too long to start, TS types got crazy, it was too much.
- XState is very good for state machines… but you barely ever need that level of control. Most of the time understanding it ends up being overhead. We’ve had maybe one good case for using it in about 10 years.
- React’s contexts are minimum overhead and work very well with “jump to definition” functionality and TS types. You can choose to use a reducer if you want, or skip it if it isn’t appropriate.
We use mobx and I think it’s great. The observable / computed model is really familiar from spreadsheets so it’s very easy to reason about code and computations. You always just think about your data in terms of being computed from dependencies without worrying about pushing state though the system.
We have an object graph (updated via web socket subscriptions) and then twist and turn slices of that data on the way up to the components. I hardly think about the fact that mobx is there and instead just think in terms of reshaping data for display.
i like mobx because it encourages you to separate state from presentation more than React Context does, but doesn't discourage keeping logically separate state trees separate the way Redux does.
I came into a fairly complex app using redux. I asked the developer about why contexts wouldn’t be a better fit, and within a week we’d dropped redux for contexts. There are certainly uses where Redux really is the right tool, but it’s nice how flexible the built in stuff is.
What do you do for modifying the data that is shared across the app? It seems like you’d either have one huge context that’s very hard to manipulate, or many deeply nested small ones?
We’re using redux (with a copious amount of selectors) and it’s the only reason I have a little bit of confidence where all our data is.
I have built a lot of apps, including huge react apps. The problem you are describing is usually a result of people representing too much in state of as part of the same state.
Now I could be wetting but it's hard to have these sorts of discussions on the abstract.
You can have many small states within a larger context, and each consumer component can choose which states it wants from the context. Basically it's a bunch of global variables.
Right but every time that context changes it triggers a re-render of every child component regardless of if it’s consuming that bit of state or not. These comments are making me really question other peoples’ understanding of what I thought were kind of basic principles of how react, context, or redux work…
Agreed. For large apps with even a little bit of complexity, context doesn't seem like much of a solution. You either have a million contexts for every shared state slice to optimize re-renders or you have a single context that takes a huge hit on performance. A single large context object is redux except redux knows how to intelligently update components based on what changed.
I don't get the hesitation to use redux. I've used it on every single FE app I've worked on and never regretted it. I can make redux do exactly what I want it to do and with the power we get from memoization of selectors via reselect, there really is nothing that beats it.
I'm also convinced that most people praising react-query are hugely missing out on the level of control you get with redux. Further, coupling side-effects -- e.g. fetching data from api -- with react component lifecycles is wrought with awkward edge-cases that you don't have to deal with in redux.
The Redux system is really hard to trace through in an IDE. It turns every simple function into lines of boilerplate spread across multiple files, and simple business logic becomes so much harder to read.
Maybe I'm just not smart enough, but I've been working with Redux for a few months and I get more confused every time I look at it, despite hours of videos and documentation and tutorials. It's just not at all clear to me what it's doing behind the scenes and how the typings get transfered and what's happening during async operations.
I don't think I'd ever want to use Redux again after this experience. I'd much rather deal with performance problems than the layers of unreadable abstractions that Redux uses...
Maybe that's where part of your confusion lies, the redux flow itself is completely synchronous. I've seen people write async dispatch(), then getting confused as to why it's not taking effect.
Probably writing lots of state machines in C helped. Redux at its core is basically just a state machine. Through functions (actions) you invoke the reducers which change the state. And then you use that state in your app.
I do agree that the documentation around redux is not very clear at the minute.
And I will add that the best tool is the one you understand the best.
Like most systems, it’s all about how it is setup. I’ve learned over 5-7 years how to organize FE (with redux) code so it is readable. Also inheriting a system can be quite the burden and I could see how it was be difficult to grok.
Just to check, are you using "modern Redux" with Redux Toolkit + React-Redux hooks, or the older-style legacy "handwritten" Redux patterns that existed before Redux Toolkit?
If you haven't seen RTK, we designed it to make Redux a lot simpler to learn and use:
It doesn't. Updating state in a component triggers the re-rendering of all its children. This is what they are referring to. You can stop that propagating effect as usual by wrapping direct descendants in React.memo().
Be aware, that an anonymous list of components will show up as "re-rendering" in the browser tools. You can verify that this is not(!) the case by looking at the profiler instead.
Additionally, if you cannot tolerate even just one component re-rendering, you can simply use React.useSynExternalStore() instead of Context.
The same is true in redux, every action calls every reducer, every connected component renders. Thats why with and without redux you need performance optimizations like memoizing components and/or selectors
Not that with just react contexts, you can chose to split them up instead of or in addition to employing memoization
That's not true though. Every connected component subscribed to a particular slice of state updates...not every connected component, e.g. `useSelector(appState => !!appState.someThing)` only causes a re-render of the subscribed component if the value of `!!appState.someThing` changes. Unless that's what you meant in the first place but your tone and talk of memoization seemed to imply otherwise.
My takeaway from these comments is that people either don’t care or literally don’t know how context works but are talking about how it’s better than redux lol
Context does not trigger the re-rendering of all children. Updating state does. And you prevent that propagating effect by wrapping direct descendants in React.memo().
React Router moved to contexts some number of versions ago...as I look into libraries for React, I see most leveraging Contexts. I've had success with it over the past year and appreciate your affirmation
Yep when I was rewriting the MVP at my current place I looked into them all and decided that context + hooks (and a server side pass for first paint) was all we needed. It was hairy at first keeping things in store etc but we wrote some lib functions that we haven’t had to touch since.
Redux is nice, but it’s be nice to have a very minimal framework for adding a global and local states via hooks and syncing their data structures to the indexed db
In my experience most of my state in contexts changes infrequently (auth information, or the “current client” for a dashboard, or whatever), and then local state is sufficient for the rest. The few times I have sufficiently complexity in the “middle” I indeed have been annoyed and had to be careful to avoid over-frequent rerenders.
It’s just rare enough that “prefer contexts” is a good rule. Simpler, easier to understand code, with no Redux nonsense.
Absolutely, and that's a major reason why we specifically teach the React-Redux hooks API as default today, and have guidance on setting up "pre-typed" hooks with the store types baked in:
Moved to context in full also but I will say Redux's toolkit thing they released is a nice addition that was much needed. I do enjoy using some RxJS still for certain problems. Agreed on XState. Want to use it but its way overdoing it often.
Sure, but as a senior I could get juniors up to speed on redux mostly within a day, the longest was 3 days. It's not that difficult if you have a working codebase they need to contribute to.
I'm genuinely curious - do you have any examples of "misusing `createSlice`"?
FWIW I see folks misusing / misunderstanding Context on a near-daily basis, mostly around misunderstanding what components will re-render and when. That's part of what led me to write my "Guide to React Rendering Behavior" post, to clarify when/why/how React renders:
A common issue with "createSlice" even with seasoned developers is they're introducing useless nesting and weird imperative-functional constructs because they fundamentally do not understand how "immer" works.
I've read your blog post, and I might be missing something, but you do not seem to be highlighting any re-rendering issues specific to context. Juniors will have to learn how React works anyway. Context or not.
- Thinking that if you change a field in the context value only components that read that field will re-render
- Thinking that _only_ the components that consume the context will re-render, not realizing that normal recursive rendering behavior resumes from there
- Not realizing that updating a context requires a `setState` call in the parent that renders the `<MyContext.Provider>`, and you can easily end up in a situation where the _entire_ component tree renders just because of default recursive rendering
Can you point to some examples of the "useless nesting" and "weird constructs" you're referring to? I'm curious to see what's happening, and if there's any docs improvements we can add to help provide guidance.
These "misunderstandings about context" are not specific to context. React developers will have to learn about them whether they are using context or not.
> Can you point to some examples of the "useless nesting" and "weird constructs" you're referring to?
Example: Create a slice with a list of items as initial state and truncate it in a reducer.
Been building in HTMX for the last month and I’m loving the new paradigm. This vid did a good job of explaining the shift. https://youtu.be/LRrrxQXWdhI
(Been using preact/react since 2016, angular/jquery before that)
Thanks for sharing this, I found this paradigm to be highly intuitive. Adding a link to the HTMX docs which also do a great job of explaining: https://htmx.org/
Adding: another reason I like it is the backend does the rendering (like server rendered react)[1] except the language doesn’t matter (agnostic) so that I’m able to use a functional language (F#) to build all my “components”.
[1]: not actually like server rendered react, since srr only happens the first page load.
I try to stick with plain React hooks when I can. For small-medium state complexity they're usually enough
For big stuff, especially deeply nested trees, MobX is still my favorite. It has some quirks but the core premise is such a simple and ergonomic mental model to work with, even for very complex state with intermediate derivations
1. It's an add-on, so eg. you have to manually wrap each React component in an observer(), it doesn't really interact with hooks (triggering or responding to updates), etc
2. It has some minor gotchas around object cloning, proxies, memory usage, and stuff like eg. certain functions have to be pure
But in practice these aren't big barriers; once you get used to them they fade into the background. And I've made stuff with MobX that I'm not sure I could have built with any other state management system. It's also one of the best-performing ways to write React apps because it can be absolutely surgical about only re-rendering exactly the things that need to update, even moreso than the official state management system
When you refer to frontend state management, I'm assuming you're talking about libraries like Redux, and are using something like React. Basically, storing state that cuts across the component hierarchy.
First of all, I would suggest minimizing it as much as possible! You can often get away with a combination of component local state and data fetching libraries like SWR (the one I use) and react-query (I've heard it's good but haven't used). Modern data fetching libraries support intelligent caching, cache invalidation, and even optimistic updates.
The vast majority of internal tools at Twitter (prev employer where we built internal tools) didn't need any state management at all outside of swr and component local state.
However there will be some cases where some sort of client side state management will be useful. In these cases I reach for jotai. It was one of the first state management solutions designed post React Hooks, so it feels much more natural than state management solutions where hooks were bolted on. Additionally, it doesn't suffer from some of the rough edges that similar libraries like Recoil have (like naming fatigue from all those string keys).
Btw, I only use a tiny subset of jotai. I don't use any of the persistence or async stuff.
So tl;dr:
1. Build your app with component local state and SWR only.
2. If this gets messy, refactor your component local state and SWR code to be abstracted away as reusable functions / hooks.
3. Only when there is a performance problem or a really convoluted component hierarchy that doesn't make much logical sense, consider adopting jotai, but only for the parts of your app state that are causing the issues.
My team is currently using this and I've been wondering if there are reasons to move to something like jotai or Zustand. The ergonomics of react-hooks-global-state with TS is quite nice already.
Assuming you mean a web-based application, then all state via HATEOAS, and CSS or JS in the front-end is strictly a non-essential progressive enhancement.
This is for commercial reasons. Apps that depend on assets are crippled over low-bandwidth or otherwise unreliable connections, and frequently break in the presence of aggressive content blockers. I won't do that to my customers.
Just use a global object and continuously run setState on the root component inside a requestAnimationFrame. Whenever you need to use or display something just reference or set the global object and you will be sure it's visually updated within the next rAF.
I'm not sure if this was a serious comment, but that sounds like a massive amount of performance overhead.
A way to optimize that approach substantially, while keeping everything else mostly the same, would be to have a setter on the global object, and only re-render the root component when that setter has updated the state.
My suggestion is not a very optimal approach, but probably a lot better than the continuous state loop.
166 comments
[ 3163 ms ] story [ 406 ms ] threadhttps://speaker.app has a UI I've been prototyping w/ this approach.
https://www.npmjs.com/package/eventemitter2
https://www.npmjs.com/package/eventemitter3
https://www.npmjs.com/package/eventemitter4
https://www.npmjs.com/package/eventemitter5
https://www.npmjs.com/package/eventemitter6
https://www.npmjs.com/package/eventemitter7
https://www.npmjs.com/package/eventemitter8
https://www.npmjs.com/package/eventemitter9
https://www.npmjs.com/package/eventemitter10
https://www.npmjs.com/package/eventemitter11
Add anything you want to keep to it, store it in the window with window.app = {}
You can mix local storage in with it too if you want to write a class that checks its existence with a key.
Why must state management need a complex solution?
I’m sick of frameworks and modes where they abstract away the simplicity of javascript in favor of their way which is very economically driven.
window.getState(); window.setState();
You could even use document.querySelectorAll() to go through all the elements on your page, and update them based off values in the state, whenever window.setState() is called. This is basically all front-end frameworks in a nutshell. You've invented reactive programming in the browser.
But for a simple thing built with JS that isn’t going to need to scale to a large codebase or whatever, sure, keep it simple!
For anything non-trivial you'd have to add some kind of listener or tag to every node, find said node, and then update (ideally in a performative way). Said updates cannot destroy existing state, but must patch it. Updates must also not break existing attached event listeners. The list goes on and on.
I don’t know and can’t easily find out the time complexity of things like React setState(), but would hope that for frontend code bases where there is a build step(s) and/or for typed frontend languages we could achieve better than linear (especially if N is every element on the page)
However, you could attach event handlers for state change monitoring & propagation.
I’m curious to know how scalable an object GP described would be for UI state (assuming it needs monitoring & propagating)
Redux state is “just an object” that does shallow comparisons to trigger re-renders
Some state does change a lot, and for that: sure. But if you just have currently logged in user or some such (which is quite often the case): meh, it just seems like a complex way to set a global variable.
This week I spent some time removing the Vuex state management in favour of window globals and it made everything a lot simpler and the bundle size a lot smaller (the other option would be to migrate to Pinia, which is significantly smaller than Vuex, but then I'd probably have to update to the next incompatible version in 2 years, and this seemed more robust).
Forgo, Mithril, and Crank all require explicit commands to rerender, unlike React and friends, which need visibility into everything that could ever trigger a rerender so they can handle it automatically.
You'd think manual rerenders would be a big pain, but I find it a small price for allowing by state to just be regular ol' variables. It makes everything feel simpler, like I'm just using the language instead of expressing my business logic through the framework.
There may be times when it's worth the cost, but otherwise just hang it on the global state and move on with your life.
https://tanstack.com/query/v4
Context: the app in a large enterprise Angular app (100+kLOC)
[0] https://conjure.so
[1] https://linear.app
The cons are:
- A fair amount of work to make changes to the state side-effect free, but the documentation has a good tutorial on how to approach this.
- It's for ClojureScript, so it's a non-starter for organizations that will not consider anything outside of the mainstream.
I have also used Elm. If I recall correctly, Elm's state management paradigm was inspired by re-frame. I found writing JSON decoders in Elm to be demoralizing and tedious. Since the creator of the language was vehemently opposed to providing any real JSON support I decided to move to ClojureScript.
Nope, it's the other way around
Sometimes you really need a state machine and for me this is the go to library.
Pros:
-Visualizer. Non-tech people love the visualizer. Tech people as well really.
-Integrates easily with entities which are also implicitly state machines, like Promise.
Cons:
-Learning curve. I've already rewritten our state machine once because my original implementation was not idiomatic.
Mobx also works. Ngrx, Redux and the like just smear your logic all over the place, which is bad.
- Redux is cool but there’s so much decoupling that it gets hard to read the code. New hires would take too long to start, TS types got crazy, it was too much.
- XState is very good for state machines… but you barely ever need that level of control. Most of the time understanding it ends up being overhead. We’ve had maybe one good case for using it in about 10 years.
- React’s contexts are minimum overhead and work very well with “jump to definition” functionality and TS types. You can choose to use a reducer if you want, or skip it if it isn’t appropriate.
YMMV of course. This is just us.
We have an object graph (updated via web socket subscriptions) and then twist and turn slices of that data on the way up to the components. I hardly think about the fact that mobx is there and instead just think in terms of reshaping data for display.
Success with MobX on a project. I worked as lead picking up an existing code base - none of us had any MobX experience so came in fresh.
My employer was kind enough to offer up FrontendMasters subscriptions - I watched a MobX course there over a couple of nights - link: https://frontendmasters.com/courses/redux-mobx/
I enjoyed it! Got the job done, no scaling issues
We’re using redux (with a copious amount of selectors) and it’s the only reason I have a little bit of confidence where all our data is.
Now I could be wetting but it's hard to have these sorts of discussions on the abstract.
Can you name examples of such state?
I don't get the hesitation to use redux. I've used it on every single FE app I've worked on and never regretted it. I can make redux do exactly what I want it to do and with the power we get from memoization of selectors via reselect, there really is nothing that beats it.
I'm also convinced that most people praising react-query are hugely missing out on the level of control you get with redux. Further, coupling side-effects -- e.g. fetching data from api -- with react component lifecycles is wrought with awkward edge-cases that you don't have to deal with in redux.
Maybe I'm just not smart enough, but I've been working with Redux for a few months and I get more confused every time I look at it, despite hours of videos and documentation and tutorials. It's just not at all clear to me what it's doing behind the scenes and how the typings get transfered and what's happening during async operations.
I don't think I'd ever want to use Redux again after this experience. I'd much rather deal with performance problems than the layers of unreadable abstractions that Redux uses...
I also heavily leverage https://github.com/neurosnap/robodux to treat redux as a database.
At the end of the day, redux is an event emitter (pub/sub) with a single object that stores all of your state that multiple components need to reuse.
If you haven't seen RTK, we designed it to make Redux a lot simpler to learn and use:
- https://redux.js.org/introduction/why-rtk-is-redux-today
- https://redux.js.org/tutorials/essentials/part-2-app-structu...
- https://blog.isquaredsoftware.com/2022/06/presentations-mode...
Does memoizing the state help?
It doesn't. Updating state in a component triggers the re-rendering of all its children. This is what they are referring to. You can stop that propagating effect as usual by wrapping direct descendants in React.memo().
Be aware, that an anonymous list of components will show up as "re-rendering" in the browser tools. You can verify that this is not(!) the case by looking at the profiler instead.
Additionally, if you cannot tolerate even just one component re-rendering, you can simply use React.useSynExternalStore() instead of Context.
Not that with just react contexts, you can chose to split them up instead of or in addition to employing memoization
Context does not trigger the re-rendering of all children. Updating state does. And you prevent that propagating effect by wrapping direct descendants in React.memo().
React Router moved to contexts some number of versions ago...as I look into libraries for React, I see most leveraging Contexts. I've had success with it over the past year and appreciate your affirmation
Redux is nice, but it’s be nice to have a very minimal framework for adding a global and local states via hooks and syncing their data structures to the indexed db
It’s just rare enough that “prefer contexts” is a good rule. Simpler, easier to understand code, with no Redux nonsense.
- https://redux.js.org/tutorials/essentials/part-2-app-structu...
- https://redux.js.org/tutorials/typescript-quick-start#define...
I had senior developers, who have worked with RTK before, misusing simple features such as the "createSlice" API.
By contrast, I have never once sat down with a junior to walk through React's Context. I assume, they just read the docs and were good to go.
FWIW I see folks misusing / misunderstanding Context on a near-daily basis, mostly around misunderstanding what components will re-render and when. That's part of what led me to write my "Guide to React Rendering Behavior" post, to clarify when/why/how React renders:
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
I've read your blog post, and I might be missing something, but you do not seem to be highlighting any re-rendering issues specific to context. Juniors will have to learn how React works anyway. Context or not.
- Thinking that if you change a field in the context value only components that read that field will re-render
- Thinking that _only_ the components that consume the context will re-render, not realizing that normal recursive rendering behavior resumes from there
- Not realizing that updating a context requires a `setState` call in the parent that renders the `<MyContext.Provider>`, and you can easily end up in a situation where the _entire_ component tree renders just because of default recursive rendering
Can you point to some examples of the "useless nesting" and "weird constructs" you're referring to? I'm curious to see what's happening, and if there's any docs improvements we can add to help provide guidance.
> Can you point to some examples of the "useless nesting" and "weird constructs" you're referring to?
Example: Create a slice with a list of items as initial state and truncate it in a reducer.
(Been using preact/react since 2016, angular/jquery before that)
[1]: not actually like server rendered react, since srr only happens the first page load.
For big stuff, especially deeply nested trees, MobX is still my favorite. It has some quirks but the core premise is such a simple and ergonomic mental model to work with, even for very complex state with intermediate derivations
2. It has some minor gotchas around object cloning, proxies, memory usage, and stuff like eg. certain functions have to be pure
But in practice these aren't big barriers; once you get used to them they fade into the background. And I've made stuff with MobX that I'm not sure I could have built with any other state management system. It's also one of the best-performing ways to write React apps because it can be absolutely surgical about only re-rendering exactly the things that need to update, even moreso than the official state management system
First of all, I would suggest minimizing it as much as possible! You can often get away with a combination of component local state and data fetching libraries like SWR (the one I use) and react-query (I've heard it's good but haven't used). Modern data fetching libraries support intelligent caching, cache invalidation, and even optimistic updates.
The vast majority of internal tools at Twitter (prev employer where we built internal tools) didn't need any state management at all outside of swr and component local state.
However there will be some cases where some sort of client side state management will be useful. In these cases I reach for jotai. It was one of the first state management solutions designed post React Hooks, so it feels much more natural than state management solutions where hooks were bolted on. Additionally, it doesn't suffer from some of the rough edges that similar libraries like Recoil have (like naming fatigue from all those string keys).
Btw, I only use a tiny subset of jotai. I don't use any of the persistence or async stuff.
So tl;dr:
1. Build your app with component local state and SWR only.
2. If this gets messy, refactor your component local state and SWR code to be abstracted away as reusable functions / hooks.
3. Only when there is a performance problem or a really convoluted component hierarchy that doesn't make much logical sense, consider adopting jotai, but only for the parts of your app state that are causing the issues.
This is for commercial reasons. Apps that depend on assets are crippled over low-bandwidth or otherwise unreliable connections, and frequently break in the presence of aggressive content blockers. I won't do that to my customers.
[1] https://jotai.org/
A way to optimize that approach substantially, while keeping everything else mostly the same, would be to have a setter on the global object, and only re-render the root component when that setter has updated the state.
My suggestion is not a very optimal approach, but probably a lot better than the continuous state loop.