Skip to content

Bug: Context.Consumer re-rendering children with unchanged context value in react 19. #33498

Open
@nathanmarks

Description

@nathanmarks

After upgrading to react 19, I noticed a performance regression due to unexpected re-rendering in my react app.

I have a setup with a context that gets updated, but some child trees override that context with their own value and do not always update when the outermost context updates.

In react 18, as expected, children consuming the context (either via useContext or Context.Consumer) did not re-render when the outermost context value updated, as long as their tree had an additional context provider that kept a stable context value.

In react 19, this behavior is maintained for useContext, but Context.Consumer re-renders its children.

React 18 example: https://codesandbox.io/p/sandbox/dqzj8j
React 19 example: https://codesandbox.io/p/sandbox/j9zrsy

The code is the exact same other than the react dep version.

You can see in the React 18 example, both ChildA and ChildB never render again as they are wrapped in memo and the context value does not change in their tree. In the react 19 example though, ChildB re-renders every time the value in the outermost context updates, even though that context is re-provided for the tree containing ChildB.


Update 16/06: I've noticed that in react 19, the useContext example DOES run the component and do most of the work involved, it just doesn't actually commit the render. I observed this by putting a console.log in the component to see if it's running.

The concern here isn't because we depend on side effects or anything like that, it's that some of our components are unfortunately quite expensive to run, and this context is consumed everywhere!

Until now, we've been running on the assumption that as long as the context value for that subtree remained stable, that components would not re-run (which was true pre react 19)

It took a while for us to understand this as this manifests as work being done to run all the consumers (visible in chrome performance profile), but then the work missing from the flamegraph in react profiler since nothing is actually rendered. (You can see it accounted for in the total render time in the timeline tab of react profiler though)

Here's what we are doing for a workaround right now to avoid the perf penalty. We're creating a second context and using useContext(FooOverride) || use(Foo) to conditionally call the "main" context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions