Prevent unnecessary re-rendering when using Context API

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,

When our app loads first, the MidChild component re-rendered.

Now what if we click the Increment button we will see value count increments by one.

As you can see we clicked the button 7 times and apart from the initial re-rendering the MidChild component re-rendered 7 times. This is completely unnecessary cause MidChild component has nothing to do with context and also it doesn’t have props. This is the core principal of Context API, when a context value changed all components re-render.

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;
Now we can see memo prevents unnecessary re-rendering. Basically memo did, memorized component MidChild, each time context value update, react checks the component MidChild received new props or not, if not it won’t re-render if yes then it will re-render. If the component has also no reference of Context then memo can’t re-render unnecessary the component else it can. MidChild component has no props it doesn’t have any context reference either. So we prevent unnecessary re-renders of MidChild component.

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.

8 thoughts on “Prevent unnecessary re-rendering when using Context API”

  1. 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.

    1. 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.

  2. 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

Leave a Comment

Your email address will not be published. Required fields are marked *