Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 332
Unknown developer Avatar asked Sep 12 '25 16:09

Unknown developer


2 Answers

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.

like image 70
Ilê Caian Avatar answered Sep 15 '25 07:09

Ilê Caian


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.

like image 26
hackape Avatar answered Sep 15 '25 07:09

hackape