Reputation: 55
In this article React Hooks - Understanding Component Re-renders, I learned that when we use useContext
Hook in parent component, only the children components which consume the context would re-render.
And the article gives two way of consumptions of context. Take a look at the snippet:
Efficient consumption of useContext\
import React from "react";
import ReactDOM from "react-dom";
import TickerComponent from "./tickerComponent";
import ThemedTickerComponent from "./themedTickerComponent";
import { ThemeContextProvider } from "./themeContextProvider";
import ThemeSelector from "./themeSelector";
import "./index.scss";
import logger from "./logger";
function App() {
logger.info("App", `Rendered`);
return (
<ThemeContextProvider>
<ThemeSelector />
<ThemedTickerComponent id={1} />
<TickerComponent id={2} />
</ThemeContextProvider>
);
}
import React, { useState } from "react";
const defaultContext = {
theme: "dark",
setTheme: () => {}
};
export const ThemeContext = React.createContext(defaultContext);
export const ThemeContextProvider = props => {
const setTheme = theme => {
setState({ ...state, theme: theme });
};
const initState = {
...defaultContext,
setTheme: setTheme
};
const [state, setState] = useState(initState);
return (
<ThemeContext.Provider value={state}>
{props.children}
</ThemeContext.Provider>
);
};
import React from "react";
import { useContext } from "react";
import { ThemeContext } from "./themeContextProvider";
function ThemeSelector() {
const { theme, setTheme } = useContext(ThemeContext);
const onThemeChanged = theme => {
logger.info("ThemeSelector", `Theme selection changed (${theme})`);
setTheme(theme);
};
return (
<div style={{ padding: "10px 5px 5px 5px" }}>
<label>
<input
type="radio"
value="dark"
checked={theme === "dark"}
onChange={() => onThemeChanged("dark")}
/>
Dark
</label>
<label>
<input
type="radio"
value="light"
checked={theme === "light"}
onChange={() => onThemeChanged("light")}
/>
Light
</label>
</div>
);
}
module.exports = ThemeSelector;
import React from "react";
import { ThemeContext } from "./themeContextProvider";
import TickerComponent from "./tickerComponent";
import { useContext } from "react";
function ThemedTickerComponent(props) {
const { theme } = useContext(ThemeContext);
return <TickerComponent id={props.id} theme={theme} />;
}
module.exports = ThemedTickerComponent;
import React from "react";
import { useState } from "react";
import stockPriceService from "./stockPriceService";
import "./tickerComponent.scss";
function TickerComponent(props) {
const [ticker, setTicker] = useState("AAPL");
const currentPrice = stockPriceService.fetchPricesForTicker(ticker);
const componentRef = React.createRef();
setTimeout(() => {
componentRef.current.classList.add("render");
setTimeout(() => {
componentRef.current.classList.remove("render");
}, 1000);
}, 50);
const onChange = event => {
setTicker(event.target.value);
};
return (
<>
<div className="theme-label">
{props.theme ? "(supports theme)" : "(only dark mode)"}
</div>
<div className={`ticker ${props.theme || ""}`} ref={componentRef}>
<select id="lang" onChange={onChange} value={ticker}>
<option value="">Select</option>
<option value="NFLX">NFLX</option>
<option value="FB">FB</option>
<option value="MSFT">MSFT</option>
<option value="AAPL">AAPL</option>
</select>
<div>
<div className="ticker-name">{ticker}</div>
<div className="ticker-price">{currentPrice}</div>
</div>
</div>
</>
);
}
module.exports = TickerComponent;
Inefficient consumption of useContext
import React from "react";
import ReactDOM from "react-dom";
import { useContext } from "react";
import TickerComponent from "./tickerComponent";
import ThemedTickerComponent from "./themedTickerComponent";
import { ThemeContextProvider } from "./themeContextProvider";
import { ThemeContext } from "./themeContextProvider";
function App() {
const { theme, setTheme } = useContext(ThemeContext);
const onThemeChanged = theme => {
setTheme(theme);
};
return (
<>
<div style={{ padding: "10px 5px 5px 5px" }}>
<label>
<input
type="radio"
value="dark"
checked={theme === "dark"}
onChange={() => onThemeChanged("dark")}
/>
Dark
</label>
<label>
<input
type="radio"
value="light"
checked={theme === "light"}
onChange={() => onThemeChanged("light")}
/>
Light
</label>
</div>
<ThemedTickerComponent id={1} />
<TickerComponent id={2} theme="" />
</>
);
}
In the Inefficient consumption of useContext example, the child component TickerComponent (2)
which didn't consume context re-rendered since the parent <App />
consumed context and re-rendered. But in Efficient consumption of useContext example, the child TickerComponent (2) didn't re-render even it's parent <ThemeContxtProvider>
re-rendered because of consumption of context.
I learned that children without React.memo will re-render when parent re-render, so why in Efficient consumption of useContext example that not happen?
Upvotes: 3
Views: 1439
Reputation: 36964
Your problem is that you are considering code like
function ComponentToRender() {
const count = React.useRef(0)
React.useEffect(() => {
console.log('component rendered', count.current++)
})
return null
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h2>You clicked {count} times!</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ComponentToRender />
</div>
);
}
and
function ComponentToRender() {
const count = React.useRef(0)
React.useEffect(() => {
console.log('component rendered', count.current++)
})
return null
}
function Clicker({ children }) {
const [count, setCount] = useState(0);
return (
<div>
<h2>You clicked {count} times!</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
{children}
</div>
);
}
function App() {
return (
<Clicker>
<ComponentToRender />
</Clicker>
);
}
equivalent. While they do the same thing, and behave more or less in the same way, the second example will render ComponentToRender
only once, even after pressing the "increment" button multiple times. (while the first one will re-render each time the button is pressed.)
The concept apply to your example as well. Your "inefficient consumption" will trigger a re-render from App
, and force a refresh to every direct child of that component. The "efficient consumption" doesn't, because that's not the case. In my simplified example, ComponentToRender
is actually rendered by App
, not Clicker
. So a change in state of Clicker
will not impact ComponentToRender
(that was just passed as children)
Another way for App
to be written, in the second example, is:
function App() {
const componentToRenderWithinApp = <ComponentToRender />
return (
<Clicker>
{componentToRenderWithinApp}
</Clicker>
);
}
this one is equivalent to <Clicker><ComponentToRender /></Clicker>
Upvotes: 1