Understanding Re-renders with React Context and how to avoid waste
Have you ever wondered why your component in React re-renders even without anything visibly changing? Let's understand this with simple examples – as if we were assembling a block game.
Basic scenario
Imagine three components, one inside the other:
<App>
<Container>
<SearchContainer>
<Search />
</SearchContainer>
</Container>
</App>
And let's say you have a Context at the top (App
) that shares some data with everyone below.
What happens when App changes?
If <App />
re-renders (for any reason), all components below it also re-render:
App -> Container -> SearchContainer -> Search
Even if only the last one (Search) actually uses the Context!
How to avoid unnecessary Re-renders
If Container
and SearchContainer
don't need to change, we can avoid the effort of re-rendering them by using:
const Container = React.memo(() => {
return <SearchContainer />;
});
What is React.memo?
It's like saying:
"Hey React, if nothing changes here, don't call me again!"
But what about when we use objects in Context?
Now for the trick:
const value = { a: 'tchaca', b: 'arrocha' };
<SomeContext.Provider value={value}>
<Search />
</SomeContext.Provider>
If App
re-renders, the value
object is recreated. And for React:
{ a: 'tchaca', b: 'arrocha' } !== { a: 'tchaca', b: 'arrocha' }
Even though they look the same, they are different objects (because the memory reference changed). Because of this, React thinks the context has changed and causes Search
to re-render unnecessarily.
The Solution: useMemo
Use useMemo to say:
"Only give me a new object if something truly changes."
const value = useMemo(() => {
return { a: 'tchaca', b: 'arrocha' };
}, [a, b]); // 'a' and 'b' are dependencies. If they don't change, the object isn't recreated.
Now, if App
re-renders but the values a
and b
are the same, the object remains the same - and React doesn't ask Search
to re-render.
Complete example:
// Context
import React, { createContext, useMemo } from 'react';
const MyContext = createContext(null);
export const MyProvider = ({ children }) => {
const [a, setA] = React.useState('tchaca');
const [b, setB] = React.useState('arrocha');
const value = useMemo(() => ({ a, b }), [a, b]);
// You might have a button to change 'a' or 'b' here to see the effect
// For demonstration, let's just show the rendering behavior
return (
<MyContext.Provider value={value}>
{ children }
</MyContext.Provider>
);
}
// Consumer
import React, { useContext } from 'react';
const Search = () => {
const { a } = useContext(MyContext);
console.log('Search rendered');
return <div>Hello: {a}</div>;
};
const SearchContainer = () => {
return <Search />;
};
const Container = React.memo(() => {
return <SearchContainer />;
});
export default function App() {
return (
<MyProvider>
<Container />
</MyProvider>
);
}
Note: In the Complete Example, I added useState
for a and b in App
to make the useMemo
dependency array [a, b]
functional in a real-world scenario. Without useState or props, a
and b
would just be static string literals inside App
and useMemo
's dependency array would essentially be empty if they weren't defined as variables outside the useMemo
call.
Conclusion
- Context is great for sharing data.
- But it re-renders children if the value changes.
- Even if the value looks the same, if it's a new object, React will think it's different.
- Use React.memo to prevent unnecessary re-renders in components that don't change.
- Use useMemo to ensure the Context value remains the same if the data hasn't changed.
Does this make sense to you?
Do you have another tip to improve performance with Context API?
Top comments (10)
nice breakdown, context stuff always tripped me up tbh - you ever feel like chasing every tiny perf issue is worth it long term, or sometimes you just gotta let a few go?
It's cool. I don't always catch all the performance problems, because the information isn't always enough. So I focus on specific issues that I can identify and solve them one by one.
Great post. I really like your posts.
Thanks bro
Please check your email. I will wait for it.
Thanks 😄
Okay. See you later
Very good breakdown
Did you see the 'tchaca'?
been cool seeing steady progress - it adds up. you think consistency or just always looking for tiny wins matters more over time?
It's cool. I don't always catch all the performance problems, because the information isn't always enough. So I focus on specific tiny issues that I can identify and solve them one by one.