React Context performance and suggestions - reactjs

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.

Related

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

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.

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.

Application design: how much Redux state?

I am currently working on building a small-ish app (6 front-end container-style components, 15 backend DB tables), in React.
I'm playing around with importing Redux, and I'm having a hard time designing my application state. It seems that I'll need to store broad-based state (e.g. user sessions) into Redux, but most of the other state I'll be pulling from the backend and won't necessarily need it to be always up-to-date 100%.
Based on all of the "redux-todo" style examples out there, it would seem that folks would store everything in their Redux store (since the state is relatively small, I suppose).
However, for my case, I'm wondering if Redux should just be a super lightweight store for only that which needs to be persisted across the app, a la an Ember-style 'service'.
Am I thinking about this correctly? Or should I be looking to store more state inside Redux?
I've struggled with that exact issue. It really takes some getting used to, but as another answer said, if you're using Redux, the idiomatic approach is to store basically everything in the Redux store. The one exception (and these turn out to be rather rare) is UI-only data such as a visibility toggle or dropdown info that sets a class or the like. That is, data that is ephemeral and has no impact on anything outside the component in which it lives. Even then, you will find that some UI-only items are easier to manage using actions and reducers as they often end up touching other items.
Edited to add: as the comment below states, the official Redux docs don't take a position on this issue. Which is one reason why so many people struggle with these issues -- you have a ton of freedom how to structure your React/Redux apps. My advice should be understood to be advice based on the experience of trying different approaches. Here are the three main reasons that I've seen for putting state into store:
more often than not, something I put into state ended up being needed somewhere else and getting it there through props required more work than if I had just put the data in the Redux store to start with.
Additionally, it's common to refactor your app to split apart components into more modular reusable pieces as your app grows and gets more complex. If you have data in state that is used in two places in a component you then won't be able split that component into smaller pieces easily.
Finally, readability. It's nice to be able to glance at your mapStateToProps call and see exactly what props are received or used by each component.
The problem is that Redux and putting everything in the store can feel like a lot of unneeded complexity when the actions and impact of the items is simple. If that's the case, I would suggest you consider dropping Redux at least at first. It will be a lot of work to refactor, but it will help you appreciate just where Redux shines. On the other hand, you may in fact not really need it for your app.
Back to your original question, yes, in my opinion, you should be putting more in the Redux store. Everything goes in there with a few very narrow exceptions. Data from the backend in particular should go into the store as a first step before getting mapped to props in the components where it's used.
The idea is to store any state your app needs to function. Your React components don't know anything about data other than what to do with it once they have it. The React/Redux "way" is that the only way they get it is from your Redux store.
So if you're fetching a list of items on app load, that goes in your store. Then your app has it for the entirety of the session. If someone leaves and comes back, or reloads the page, it fetches again, and stores a fresh copy in Redux again. You may not create any actions to modify it once it's there, and you may not need to fetch it again any time soon, but that's where it should live so your components can get stuff from it.
If you didn't store it in Redux where would you put it? And how would your components gain access to it?

Redux actions/reducers vs. directly setting state

I'm new to Redux. I'm having trouble understanding the value of actions and reducers vs. components directly modifying the store.
In Redux, your React components don't change the store directly. Instead they dispatch an action -- sort of like publishing a message. Then a reducer handles the action -- sort of like a message subscriber -- and changes the state (more precisely, creates a new state) in response.
I feel like the pub/sub-like interaction adds layers of indirection that make it harder to understand what a component is actually doing -- why not just allow components to pass new state to the Redux store directly? Would it be a bad thing to inject something like this.props.setReduxState into a React component?
I'm starting to understand the value of why the state itself needs to be immutable (related question -- Isn't Redux just glorified global state?), related to checking for updates to see which component props need to be updated in response to state changes. My question is the extra action/reducer layers vs. manipulating the store directly.
I had some very similar questions when I decided on going down the Redux rabbit hole. In my case I was developing relatively small apps and components using just the React internal state, which still works really well. It was only when the app and the number of components grew much larger that setState became somewhat unwieldy.
I don't think this is emphasized enough - you don't always have to use Redux if you're working on a relatively small app or a few components that are easy enough to keep track of without a robust immutable store solution. There is a lot of necessary boilerplate just to get started, which can be overly time consuming in some instances.
That being said, React + Redux is a great design paradigm to follow once you are used to it and have your own boilerplate you can call upon. Redux essentially favors props over state, which forces immutable design as you (generally) can't mutate props. This is what makes the Redux DevTools "time machine" possible, along with all of the middleware that you can throw at your store as previously mentioned by others.
Middleware is a big part of it, especially with the advent of more precise solutions for sync and async tasks alike like redux-saga (https://redux-saga.js.org/). IMO "thunks" (https://github.com/gaearon/redux-thunk) are easier to grok than sagas when you're just getting started, unless you're already an expert with generators, but sagas are much more predictable and therefore testable in the long run.
So instead of having a separate internal state for each component, you can have a set of actions / reducers that you use instead, knowing that you can't mistakenly mutate the state. I've found the combination of ImmutableJS (https://facebook.github.io/immutable-js/) and Reselect (https://github.com/reactjs/reselect), which gives you composable selectors - to be great for this. No need for Object.assigns or tons of spread operators, it creates a new object for you.
I wouldn't be in a hurry to convert existing non-Redux projects into Redux, the workflow is different enough to cause significant confusion, but you'd be hard pressed to find boilerplates for new projects that don't already have Redux in their workflow. Such as "React Boilerplate" (https://github.com/react-boilerplate/react-boilerplate), I know this kind of blew my mind at first, but definitely worth getting a feel for. It really tests your functional programming chops.
In the development process, you often need to know who and how has changed the state. Mutating state by emitting actions allows you to keep answers to those questions.
Actions are payloads of information that tell the store how it should be modified. This information is represented in form of plain javascript objects which allows this information to be logged, serialized and stored. Since all the history is "remembered", you can later replay all the chain of actions for debugging or testing purposes. Together with a tool like Redux DevTools it makes development process really easy and amazing. Since all the store modifications are logged to a monitor, so you can see how and when was the state modified on every step. Even more, you can go back or forward through the actions chain.
Another benefit of having all mutations centralized in one place is the fact that it's easier to take control over the state. That guarantees, that all mutations happen one by one in a strict order and no callbacks can make the application behavior unstable. It also allows to keep in one place functionality that is common for some actions, or in other words to apply middlewares.

Resources