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
Advertisement
Answer
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.
const App = ({ children }) => { const [theme, setTheme] = useState(false); console.log("App running"); return React.createElement(ThemeContext.Provider, { value: { theme: theme, setTheme: setTheme }, children: children }); };