Questions
This component re-renders unnecessarily due to context object reference changes. How do you fix it?
A developer has a UserContext that provides a user object containing id and name. They also have a UserProfile component that consumes this context to display the user’s name.
They’ve noticed a performance issue: UserProfile re-renders every time its parent component re-renders, even if the user object’s content (id and name) hasn’t changed. This is causing unnecessary work on a complex profile page.
You’ve been given the code that demonstrates this issue.
- Explain why the
UserProfilecomponent re-renders when the parent component re-renders, even if theuserdata is logically the same. - Optimize the
UserProviderto prevent these unnecessary re-renders inUserProfile.
import React, { useState, useContext, createContext } from 'react';
// User Context
const UserContext = createContext();
// Component consuming the UserContext
function UserProfile() {
const { user } = useContext(UserContext);
console.log('Re-rendering UserProfile...'); // This should not happen on parent re-render if user data is same
return (
<div>
<h2>User Profile</h2>
<p>ID: {user.id}</p>
<p>Name: {user.name}</p>
</div>
);
}
// User Provider
function UserProvider({ children }) {
const [userId, setUserId] = useState(1);
const [userName, setUserName] = useState('Alice');
// THE BUG: The user object is created inline on every render.
// Its reference changes even if userId and userName are the same.
const user = { id: userId, name: userName };
// This context value object's reference changes on every render of UserProvider
const contextValue = { user };
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
}
export default function App() {
const [count, setCount] = useState(0); // State to force App re-renders
return (
<UserProvider>
<div style={{ padding: '20px' }}>
<h1>Application</h1>
<button onClick={() => setCount(c => c + 1)}>
Force App Re-render (Count: {count})
</button>
<hr />
<UserProfile />
</div>
</UserProvider>
);
} Creating the user object and contextValue inline on every render causes new object references each time, even if the values haven't changed. This triggers re-renders in all consuming components because React detects a reference change in the context value.
Use `useMemo` to memoize both the user object and the contextValue. The memoized objects only get new references when their dependencies (userId, userName) actually change, preventing unnecessary re-renders in consuming components while maintaining logical equality checks.
The problem stems from how JavaScript handles objects and how React’s useContext hook detects changes.
In the UserProvider component, the user object ({ id: userId, name: userName }) is created directly within the component’s render function. Even if userId and userName remain the same, every time UserProvider re-renders (e.g., when its parent App re-renders due to setCount), a new object reference for user is created.
When this new user object is then placed into contextValue (which also becomes a new object reference), React sees that the value prop passed to UserContext.Provider has changed (because its reference is different). Consequently, useContext in UserProfile detects a change in the context value and triggers a re-render of UserProfile, even though the actual id and name properties within the user object are identical.
To prevent this unnecessary re-render, we need to ensure that the contextValue object’s reference only changes when its actual dependencies (userId or userName) change. This can be achieved using the useMemo hook.
useMemo will memoize the contextValue object. It will only re-create the object (and thus change its reference) if any of its dependencies (specified in the dependency array) have changed.
Here is the corrected implementation:
import React, { useState, useContext, createContext, useMemo } from 'react';
const UserContext = createContext();
function UserProfile() {
const { user } = useContext(UserContext);
console.log('Re-rendering UserProfile...');
return (
<div>
<h2>User Profile</h2>
<p>ID: {user.id}</p>
<p>Name: {user.name}</p>
</div>
);
}
function UserProvider({ children }) {
const [userId, setUserId] = useState(1);
const [userName, setUserName] = useState('Alice');
// FIX: Memoize the user object and the context value.
// The user object will only be re-created if userId or userName change.
const user = useMemo(() => ({ id: userId, name: userName }), [userId, userName]);
// The contextValue object will only be re-created if the 'user' object's
// reference changes (which is now controlled by the useMemo above).
const contextValue = useMemo(() => ({ user }), [user]);
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
}
export default function App() {
const [count, setCount] = useState(0);
return (
<UserProvider>
<div style={{ padding: '20px' }}>
<h1>Application</h1>
<button onClick={() => setCount(c => c + 1)}>
Force App Re-render (Count: {count})
</button>
<hr />
<UserProfile />
</div>
</UserProvider>
);
}With useMemo, the user object and contextValue object references will remain stable across UserProvider re-renders as long as userId and userName don’t change. This prevents UserProfile from re-rendering unnecessarily.
Practice Question
How does `useMemo` help optimize a React Context Provider's `value` prop?