DeployU
Interviews / Frontend Engineering / An expensive component is re-rendering unnecessarily. How would you fix this state management issue?

Questions

An expensive component is re-rendering unnecessarily. How would you fix this state management issue?

architecture Performance Interactive Quiz Code Examples

The Scenario

A junior developer built a new feature using React Context to manage the application’s global state. The state object contains everything: the current user’s info, and the UI theme (light/dark mode).

They’ve noticed a performance problem. The UserWelcome component, which is expensive to render, re-renders every single time the theme is changed. This is making the UI feel sluggish.

The Challenge

You’ve been given the code that demonstrates this issue.

  1. Explain why the UserWelcome component re-renders every time the “Toggle Theme” button is clicked.
  2. Describe two different strategies to fix this performance issue so that UserWelcome only re-renders when the user data it cares about actually changes.
Context Performance Issue - Fix Unnecessary Re-renders
Your Code
import React, { useState, useContext, createContext } from 'react';

// The monolithic context holding all global state
const AppContext = createContext();

// A component that is "expensive" to render
function UserWelcome() {
  const { user } = useContext(AppContext);
  console.log('Re-rendering UserWelcome (expensive)...');
  return <h1>Welcome, {user.name}!</h1>;
}

// A component to change the theme
function ThemeToggler() {
  const { theme, setTheme } = useContext(AppContext);
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle Theme
    </button>
  );
}

// The main App provider
export default function App() {
  const [user, setUser] = useState({ name: 'Alice' });
  const [theme, setTheme] = useState('light');

  const contextValue = { user, setUser, theme, setTheme };

  return (
    <AppContext.Provider value={contextValue}>
      <div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff', padding: '20px' }}>
        <ThemeToggler />
        <hr />
        <UserWelcome />
      </div>
    </AppContext.Provider>
  );
}
// Validate the fix
const code = document.querySelector('[data-editor]').textContent;

// Check for split contexts (best solution)
const hasThemeContext = code.includes('ThemeContext') && code.includes('createContext()');
const hasUserContext = code.includes('UserContext') && code.includes('createContext()');

if (hasThemeContext && hasUserContext) {
  console.log('✓ Excellent! You split the contexts - this is the best practice solution.');
  console.log('✓ UserWelcome now only subscribes to UserContext and won\'t re-render on theme changes.');
} else {
  throw new Error('Test failed: Split the monolithic AppContext into separate ThemeContext and UserContext.');
}

// Verify components use the correct context
if (!code.match(/UserWelcome[\s\S]*?useContext\(UserContext\)/)) {
  throw new Error('Test failed: UserWelcome should use useContext(UserContext), not AppContext.');
}

if (!code.match(/ThemeToggler[\s\S]*?useContext\(ThemeContext\)/)) {
  throw new Error('Test failed: ThemeToggler should use useContext(ThemeContext), not AppContext.');
}

console.log('✓ All tests passed! Context performance issue fixed correctly.');
Click "Run Code" to see output...
Click "Run Code" to see test results...

The Explanation: The Problem with Monolithic Context

How Different Experience Levels Approach This
Junior Engineer
Surface Level

[object Object]

Senior Engineer
Production Ready

[object Object]

What Makes the Difference?
  • Context over facts: Explains when and why, not just what
  • Real examples: Provides specific use cases from production experience
  • Trade-offs: Acknowledges pros, cons, and decision factors

The Performance Issue

The performance issue is a classic pitfall of using React Context. When a component consumes a context (with useContext), it subscribes to all changes in that context’s value.

In this code, we have a single, monolithic AppContext that holds both user and theme. The UserWelcome component only needs user, but because it’s subscribed to the entire AppContext, React will re-render it whenever any value in the context provider’s value object changes. Clicking “Toggle Theme” creates a new contextValue object with a new theme, triggering a re-render in every component that consumes AppContext, including the expensive UserWelcome.

Here are two ways to solve this:

Solution 1: Split the Contexts (Best Practice)

The most robust solution is to separate unrelated state into different contexts. Components can then subscribe only to the state they care about.

import React, { useState, useContext, createContext } from 'react';

// Create two separate contexts
const ThemeContext = createContext();
const UserContext = createContext();

function UserWelcome() {
  // Subscribe ONLY to the UserContext
  const { user } = useContext(UserContext);
  console.log('Re-rendering UserWelcome (expensive)...');
  return <h1>Welcome, {user.name}!</h1>;
}

function ThemeToggler() {
  // Subscribe ONLY to the ThemeContext
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle Theme
    </button>
  );
}

export default function App() {
  const [user, setUser] = useState({ name: 'Alice' });
  const [theme, setTheme] = useState('light');

  // Nest the providers
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={{ user, setUser }}>
        <div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff', padding: '20px' }}>
          <ThemeToggler />
          <hr />
          <UserWelcome />
        </div>
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Now, when the theme changes, only the ThemeContext.Provider gets a new value, and only its consumers (ThemeToggler and the div) will re-render. UserWelcome is unaffected.

Solution 2: Memoization

A quicker, but often more brittle, solution is to use React.memo to wrap the expensive component. React.memo is a higher-order component that prevents a component from re-rendering if its props haven’t changed.

// Wrap the expensive component in React.memo
const MemoizedUserWelcome = React.memo(function UserWelcome() {
  const { user } = useContext(AppContext);
  console.log('Re-rendering UserWelcome (expensive)...');
  return <h1>Welcome, {user.name}!</h1>;
});

// ... in the App component ...
// <MemoizedUserWelcome />

This doesn’t work by itself! React.memo does a shallow comparison of props, but MemoizedUserWelcome has no props. It still re-renders because the context value it depends on changes. To fix this, you have to combine memo with extracting the component and passing state down as props. This shows the complexity and why splitting contexts is often better. A better memoization strategy involves splitting the component and memoizing the child.

A more advanced technique is to use useMemo to create a stable value for parts of the context, but this can get complicated quickly. Splitting contexts is almost always the cleaner, more scalable solution.

Practice Question

What is the primary performance drawback of using a single, large React Context for unrelated pieces of state?