javascriptreactjsreact-context

If a child component consumers a context provider, do its parents also re-render?


Although the docs for context say "React automatically re-renders all the children that use a particular context starting from the provider that receives a different value.", from my testing it appears that the parents that contain that child also re-render because their prop children changes.

In the following example I created a context that holds a counter value. That value comes from a state variable that is incremented on click of a button at the top level. is composed within two parent components.

When running the profiler, on click the parents rerender because children has changed.

Does that mean the doc is inaccurate?

import React, { createContext, memo, useContext } from 'react';

const Parent1 = memo((props: any) => {
  return props.children;
});
Parent1.displayName = 'Parent1';

const Parent2 = memo((props: any) => {
  return props.children;
});

Parent2.displayName = 'Parent2';

const Child = memo((props: any) => {
  const counterFromContext = useContext(TestContext);

  return (<div>
    {counterFromContext}
  </div>);
});

Child.displayName = 'Child';

const TestContext = createContext(0);

const Test = () => {

  const [counter, setCounter] = React.useState(1);

  return (
    <>
      <TestContext.Provider value={counter}>
        <Parent1>
          <Parent2>
            <Child></Child>
          </Parent2>
        </Parent1>
      </TestContext.Provider>
      <button onClick={() => setCounter((prev) => prev + 1)}>
      Update counter
      </button>
    </>
  );

};

export default memo(Test);


Solution

  • It appears that the question results from a misconception of the meaning of "children" in the context of quoted document. The component's children are the elements that are returned from component function, they don't necessarily depend on children prop, despite the common name.

    In its current state Parent1 and Parent2 inevitably re-render because they receive new element objects as children, and new element objects are created when the state is updated in Test. memo can't prevent this, at least without using custom arePropsEqual function to deeply compare element objects.

    In this case the example would work as expected if it were:

    const Parent1 = memo((props: any) => {
      return <Parent2/>;
    });
    
    const Parent2 = memo((props: any) => {
      return <Child/>;
    });
    
      ...
    
      <TestContext.Provider value={counter}>
        <Parent1/>
      </TestContext.Provider>
    

    This is a case the document describes.