In this post we are going to prevent an unnecessary re-rendering when using Context API. Context API is great but we need to be aware of some limitations, this post we will understand one of the limitation which is unnecessary re-rendering of Child Components.
Lets write our App component,
import { useState } from 'react';
import CounterContext from './CounterContext';
import Navbar from './components/Navbar';
import Child from './components/Child';
import MidChild from './components/MidChild';
function App() {
const [count, setCount] = useState(0)
const incCount = () => setCount(prev => prev + 1);
const decCount = () => setCount(prev => prev - 1);
const counterObj = {
count,
incCount,
decCount
}
return (
<CounterContext.Provider value={counterObj}>
<Navbar />
<Child />
<MidChild />
</CounterContext.Provider>
);
}
export default App;
The App component provides the CounterContext to each of its child component. And our CounterContext file,
import { createContext } from 'react';
const CounterContext = createContext({
count: 0,
incCount: () => {},
decCount: () => {}
});
export default CounterContext;
Our Navbar component is just a simple component,
import { useContext } from "react";
import CounterContext from "../CounterContext";
const Navbar = () => {
const context = useContext(CounterContext);
return (
<div className="navbar-wrapper">
<h1>Count value: {context.count}</h1>
</div>
)
}
export default Navbar;
Child and MidChild components are also similar.
import { useContext } from "react";
import CounterContext from "../CounterContext";
const Child = () => {
const context = useContext(CounterContext);
return (
<div className="child-wrapper">
<h1>This is child</h1>
<button onClick={context.incCount}>Increment</button>
</div>
)
}
export default Child;
const MidChild = () => {
console.log("mid child re rendered")
return (
<div className="mid-child-wrapper">
<h2>This is mid child</h2>
</div>
)
}
export default MidChild;
The MidChild is simple a component which logs a message each time it re-renders.
Now if we run our application we will see,
Now what if we click the Increment button we will see value count increments by one.
In order to prevent this we can use memo
which will skip unnecessary re-renders of that component.
import { memo } from "react";
const MidChild = memo(() => {
console.log("mid child re rendered");
return (
<div className="mid-child-wrap">
<h2>This is mid child</h2>
</div>
)
})
export default MidChild;
Another way to do that is to render components as children.
import { useState } from 'react';
import CounterContext from './CounterContext';
import Navbar from './components/Navbar';
import Child from './components/Child';
import MidChild from './components/MidChild';
function AppProvider({ children }) {
const [count, setCount] = useState(0)
const incCount = () => setCount(prev => prev + 1);
const decCount = () => setCount(prev => prev - 1);
const obj = {
count,
incCount,
decCount
}
return (
<CounterContext.Provider value={obj}>
{ children }
</CounterContext.Provider>
)
}
function App() {
return (
<AppProvider>
<Navbar />
<Child />
<MidChild />
</AppProvider>
);
}
export default App;
In that case we don’t need memo anymore. Now we have two option two solve that issue, memo & children. Choose anyone you like.
I’m interested in knowing how does rendering components as children solve the problem? Using react memo in an application where you have 15-20 context and about 200 components doesn’t sound like a great idea. Also, you cannot simply wrap *each and every component* into memo cause then it’s again a performance bottleneck (if not cpu, then ram). Would like to hear your thoughts on this one.
Hello. Any update on this one, please?
I just gave an answer. Sorry for late.
This is by design. You can see in step 2 and 3 https://github.com/facebook/react/issues/15156#issuecomment-474590693.
I am not fully sure wrapping components with memo cause a perf issue. As they said to use memo.
You can follow the second step I provide.
Which version of React are you using?
Hi, thanks for the article!
I also want to add that the states must be defined inside the AppProvider – which is the same as your example. If we pass them as props to the AppProvider, the children will still rerender
Try this:
https://github.com/lovetingyuan/react-atomic-context
This library can help solve unnecessary re-renders caused by React Context and allows fine-grained control over the reading and writing of each property.