Thought experiment feedback on preventing unnecessary re-renders for react/redux - reactjs

So while the virtual dom and diffing are what makes React fast compared to manipulating the dom directly, I've read that if you don't modularize your components it will trigger unnecessary renders because it will re-render all parent components that it received its props from even if their UI did not change. This is one of the biggest performance drawbacks of React.
On the very extreme-side, if I design my app so there are no child components and each one maintains its own state, it may side-step this performance issue but it would be almost impossible to maintain.
What if I create some sort of higher-order component (like redux's connect) that would wrap my entire main component (which holds all my components) and recursively creates a slice of state for every single component that holds only the props it needs? That way it can retain a redux-like singularity principle and code maintainability while avoiding unnecessary re-renders.
I'm thinking it might have to be used in conjunction with redux in order to have access to all of the props at once somewhere, but maybe it can be used without. This way code-splitting and lazy loading the components will be easy to implement (perhaps setting a max number of concurrent server requests to reduce overhead). I would memoize the props to reduce the number of requests as well.
I was thinking of it as a potential idea for side project but I don't know if it is feasible or if it solves the unnecessary rendering problem. I'm thinking of it as some sort of npm library that can be used on top of redux.

Related

React Context performance and suggestions

This is a phrase you can find in many sites and it is considered (?) as valid:
React Context is often used to avoid prop drilling, however it's known
that there's a performance issue. When a context value is changed, all components
that use useContext will re-render.
Moreover:
The React team has proposed a useSelectedContext hook to prevent performance issues
with Context at scale. There is a community library for that: use-context-selector
However, for me the above does not make any sense. Don't we want to re-render all the components that use useContext? Absolutely! Once the context value changes, all components using it must re-render. Otherwise, UI won't be in sync with the state. So, what exactly is the performance issue?
We could discuss how not to re-render the other child components of the Context Provider that do not use useContext and this is achievable (react docs):
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
By using the above pattern, we can avoid re-rendering of all child components that do not use useContext.
To recap: In my opinion, there is no performance issue when using Context the right way. The only components that will re-render are the ones that should re-render. However, almost all references insist on an underlying performance issue and stress that as one of Context's caveats. Am I missing something?
Note: I'll answer your questions and give a final comment on what I could gather on the internet and from personal experience.
TL;DR: The performance issue is a constant reminder that when you use Context API even if not explicitly writing it you are virtually passing those props to the components that use a Context State and on each state change for every component that state is accessed, those components will be re-rendered.
Answering each question from your post:
Question 1: Don't we want to re-render all the components that use useContext?
Yes, that's exactly it, as you stated.
Question 2: So, what exactly is the performance issue?
On prop changes, components re-render. When using Context API, even if not explicitly passing the Context state as a prop, each state change will trigger a re-render on that component and the child components that depend on or receive that state as a prop. You can read this on this doc such as:
Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree
This is not exactly an issue as the docs suggest you use it to store global state that does not change that much such as:
Theme
Authentication/Login state
Language/i18n
Those are the type of data that:
if changed, should trigger a "global" re-render as it'll affect the App, as a whole
do not change that much (or do not change at all) on each App interaction
will be used on different nesting levels
Question 3: Am I missing something?
Well, as you could already suppose, there are a lot of cases that don't match that type of usage of a "global state" that doesn't change that much. And, for those, there are some other options that could be used to take care of the same set of cases that Context API solve, but with a lot more overhead at the code. One of them is Redux, which doesn't have this overhead for the most obvious reason: Redux creates a parallel store from your App and doesn't pass the values as props to each and every component. On the other hand, one of the most noticeable overheads is the tooling that the project should have to accommodate that library.
But why people started using Redux(and other libs) in the first place?
Handling Global State in past versions of React was a thing. You could solve this with a lot of different ways, opinions, and approaches. Sooner or later, people started to create and use tools and libs that handles that with approaches that were considered "better" for specific or personal reasons.
Later, those tools/libs started to get more complex and with more possible "connectors" or "middlewares" here and there. As an example, one tool that can be added to Redux to handle requests is the lib called Redux Thunk, which allows performing requests inside actions (if I'm not wrong) breaking the concept of just writing actions as pure functions from Redux. Nowadays, with the growth of React Query/TanStack Query, the state related to the requests is starting to be handled also as a parallel "global state" even with cache and a lot more features, dropping the usage of Redux Thunk and Redux as a consequence to solve the "global state" from requests.
After the release of Context API to a stable and improved version, people started to use it for a lot of projects and as a Global State manager. And sooner or later everyone started to notice performance issues related to too many re-renders due to several props changing every time, everywhere. Some of them just went back to the Redux and other libs but for others, turns out that Context API is very good, practical, involves less overhead, and is embedded with React if used as it was intended. The requirements for not having to deal with the performance issues are the same as described before:
a global state that does not change so frequently and
will be used on different nesting levels
There are some other options that Context API works smoothly if you don't nest too many components. As an example: Multi-page Forms if you create the context at the Route level and not at the App level. As you also stated:
In my opinion, there is no performance issue when using Context the right way
But you could say that for almost every tool that is used out of its original usage conception.
Edit: After the OP pointed out and after reading about the Context API in the official docs, props are not passed to each and every child, just for those who use the context. And as a consequence: To each and every child component for those components that pass those props to them.
And, answering the question on why Context API has performance issues, I'm planning to create a repo to reproduce and understand but my bet is: It is probably related to the fact that each Context is called as "component" and React handles that itself, instead of creating a "parallel structure" such as Redux/Jotai, as an example.
To recap: In my opinion, there is no performance issue when using Context the right way. The only components that will re-render are the ones that should re-render.
If your Provider only provides a simple value, your recap is true.
But in reality the Provider often provides a big tree object containing many branches and leaves. However each Consumer may only need a small portion of it, even a single leaf value on that tree.
In this case there is a perf problem, cus the Context API is a whole sale solution. Even if you update a single leaf value, you still need to update the tree root object’s reference in order to signal a change. But that in turn notifies every single Consumer that useContext.
Now here’s the point that you’re missing:
It’s true that every one of them should be notified of a change, but it’s not true that all of them should re-render. Optimally only those Consumer which depend on the updated leaf value should re-render.
At its current state Context API doesn’t provide any fine grained control over this issue, thus things like use-context-selector bring back the selector pattern back into our sight.
Fundamentally this is a pub-sub model, and if you don’t have a mechanism to allow subs to decide which channel to tune in, the only thing you can do is to broadcast to all subs about everything. It’s like waking everybody in the neighborhood up just to tell them “Alice got a new mail”, which obviously is not optimal.
The very same problem exists in barebone Redux setup. Which is why selector pattern from react-redux used to be very popular.

Which communication is faster in React, Sibling-Parent-Sibling or Sibling-Store-Sibling?

I'm setting up communication between some sibling components in a React-Redux app.
What needs to be communicated is a single data value that really has no other purpose in the app outside of the child component that will display it.
It caused me to wonder if sending the data up to the parent and back down to the sibling is faster than dispatching an action and state change in the Redux store and letting the sibling receive the change that way.
My assumption is that for such a simple case just passing it up to the parent and back down is faster.
Certainly coding it through the parent will be faster than setting up the Redux actions.
They're more or less the same, actually.
Passing the value either way (through Redux or through a parent) works the same -- React will virtually re-render the component tree, and if it's different, apply those changes to the DOM. To optimize your app, optimize your render times, rather than how you pass state around.
That said, doing it through Redux will most likely make your app more complex. You would have to make actions and reducers, and maintain that chunk of state in Redux. As you said it's a pretty local piece of information, it's probably better from an architectural standpoint to keep the state in the parent component.
In my opinion, both are equally same.
If you are using react-redux architecture then it is better to store the data in redux store as it not only provides the uniformity in your code but also makes your code scalable i.e. in case the same data is consumed in other component in future then there will not be any further steps involved.
I disagree with the point that redux makes the app complex.

Do a large number of instances of a component wrapped in a redux container negatively impact performance?

What I'm thinking about doing is wrapping a ListItem component in a container, but there could be hundreds of ListItems.
Wouldn't that severely impact performance? There's essentially hundreds of ListItems each individually connected to a redux store.
Whereas before I had a List component wrapped in a container that passes the data and props down to dumb ListItem components.
I just recently read this article: https://reactrocket.com/post/react-redux-optimization/
Which states you can have a large number of connected components and in fact can perform better due to conditional re-rendering on the container side. But I've always heard that connected components should be more top-level components and assumed that connected components used listeners similar to flux.
Does anyone have thoughts about this implementation?
If you refer to the current redux docs, there is a FAQ item that addresses your question. The original docs did originally encourage connecting only top-level components, but that has been recognized as a mistake now.
Emphasizing “one container component at the top” in Redux examples was
a mistake. Don't take this as a maxim. Try to keep your presentation
components separate. Create container components by connecting them
when it's convenient. Whenever you feel like you're duplicating code
in parent components to provide data for same kinds of children, time
to extract a container. Generally as soon as you feel a parent knows
too much about “personal” data or actions of its children, time to
extract a container.
In fact, benchmarks have shown that more connected components
generally leads to better performance than fewer connected components.
In general, try to find a balance between understandable data flow and
areas of responsibility with your components.
So I would say there is nothing wrong with making each of your ListItem's a connected component. In a lot of cases, either approach will perform fine, so I would choose whatever feels the most natural. The performance is going to depend on how frequently the data going into ListItem changes relative to the parents of ListItem, and how complicated each ListItem is. If ListItem is pretty complicated, having it be connected would probably be a better bet, since you'll avoid unnecessary work when ListItem's parents changes but the props going into ListItem remain the same.
As always, benchmarking the two approaches would give you the best idea of how each will perform for your specific case.

React, rendering components based on user priviliges/roles

I am wondering what would be the best approach to implement a mechanism of rendering components based on user privileges/roles. My first thought was to implement a higher order component which will be connected to redux store and conditionally render or not wrapped components. It should work just fine but I am worrying about performance of such solution. I will probably end up with dozens of such wrapped components.
Is there a better way to handle this cleanly without too much performance overhead?
Seems like your described solution is no different than having hundreds/thousands of Redux connected components in general, in which case performance should not be an issue.
see:
https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md

Is it bad practice to pass props through multiple components down to a child?

Is it bad practice / will it hurt performance if I pass props from a root component down to a child component 4-5 components down often?
Im thinking every component in the chain will have to rerender every time the props change, meaning bad performance, since its only a small child component inside the large component which actually needs to change.
If this is indeed the case, should I flatten my root component, or should I implement a custom 'shouldComponentUpdate', or something else?
Implementing shouldComponentUpdate is very good practice to minimize re-rendering, especially if the components are kept simple and small to be able to define the shouldComponentUpdate simple as well.
As an alternative, look into react-redux or other state container to manage state easier without much hassle.
The need should be rare, but React's diff-ing algorithm will ensure nothing updates unless it is necessary. Performance should not suffer.
For instance, I usually start my apps with a container class which does a media query (using react-responsive). Using this media query I pass either display="desktop" or display="mobile" as a prop down the next component. I usually pass this.props.display all the way down my component tree and it is the deciding factor in how my CSS looks (either the desktop view or the mobile view). In this case, performance is actually enhanced!
React won't re-render the DOM unless the tree actually requires an update/change therefore no rendering performance costs.
You can also use shouldComponentUpdate() to manage your updates to each component.
Facebook's React Reconciliation
shouldComponentUpdate

Resources