Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Context Provider all children re rendering

can someone explain to me why the next code re renders all children components from the provider

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

const App = () => {
    const [theme, setTheme] = useState(false);
    console.log("App running");
    return (
        <ThemeContext.Provider value={{ theme, setTheme }} children={<Child1 />} />
    );
};

const Child1 = () => {
    console.log("Child1 running");
    return (
        <div className="child1">
            <Child2 />
        </div>
    );
};

const Child2 = () => {
    console.log("Child2 running");
    return (
        <div className="child2">
            <Child3 />
        </div>
    );
};

const Child3 = () => {
    const { theme, setTheme } = useContext(ThemeContext);
    console.log("Child3 running");
    return (
        <div className="child3">
            <p>{theme ? "dark" : "light"}</p>
            <button onClick={() => setTheme(!theme)}>Change theme</button>
        </div>
    );
};

export default App;

console everytime button is clicked, all components re rendering

App running
Child1 running
Child2 running
Child3 running
App running
Child1 running
Child2 running
Child3 running

but if context provider is wrapped in a component as follows

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
    const [theme, setTheme] = useState(false);
    console.log("ThemeProvider running");
    return (
        <ThemeContext.Provider value={{ theme, setTheme }} children={children} />
    );
};

const App = () => {
    console.log("App running");
    return <ThemeProvider children={<Child1 />} />;
};

const Child1 = () => {
    console.log("Child1 running");
    return (
        <div className="child1">
            <Child2 />
        </div>
    );
};

const Child2 = () => {
    console.log("Child2 running");
    return (
        <div className="child2">
            <Child3 />
        </div>
    );
};

const Child3 = () => {
    const { theme, setTheme } = useContext(ThemeContext);
    console.log("Child3 running");
    return (
        <div className="child3">
            <p>{theme ? "dark" : "light"}</p>
            <button onClick={() => setTheme(!theme)}>Change theme</button>
        </div>
    );
};

export default App;

console when button is clicked

ThemeProvider running
Child3 running
ThemeProvider running
Child3 running
ThemeProvider running
Child3 running

only the component consuming the context (and the component context provider) are re rendering

how exactly react manages this situation

EDIT:

react version is 17.0.1 btw

like image 675
William Estrada Avatar asked Jan 09 '21 02:01

William Estrada


1 Answers

This happens because <Context.Provider> re-renders when its children prop does not share reference equality with the previous children prop.

In the first example, every time App is re-rendered, a new Child1 React element is created.

It's basically like if you were doing something like this:

const App = () => {
  const [theme, setTheme] = useState(false);
  console.log("App running");
  return React.createElement(ThemeContext.Provider, {
    value: {
      theme: theme,
      setTheme: setTheme
    },
    children: React.createElement(Child1, null) <= Notice how the children prop is new with every re-render
  });
};

which eventually re-renders Child1, Child2 and Child3.


In the second example, the React element Child1 is created once inside App, and it's passed down to ThemeProvider, which means that inside ThemeProvider you are actually referencing the same React element, and not creating a new one with every re-render, so in this case only the associated consumer component (Child3) will re-render.

Good read about why this happens

like image 94
Hamza El Aoutar Avatar answered Sep 23 '22 01:09

Hamza El Aoutar