This Context API Mistake Ruins Your Whole React App (All Components Re-Render)
Tags: javascript, react, webdev, programming
Published: 2023-09-11 22:34:32

We can't overstate how much of a key role performance plays when it comes to user experience.

Nothing can make a user leave your app quicker than a sluggish or laggy user interface (UI), and often times this results from poor coding practices on the part of the developer.

A lot of React developers use the context API in a way that results in pointless UI rerenders and ultimately a slow application. While its effect might be subtle in smaller apps, it becomes quite noticeable in large applications.

So what is this bug we’re going to be talking about? Read on to find out!

> Sidenote: If you’re new to learning web development, and you’re looking for the best resource to help with that, I strongly recommend [HTML to React: The Ultimate Guide](https://gumroad.com/a/834147443/dvfyN).

## The Problem of Rerendering The Whole Application

Consider the following App component, which returns a main element housing two custom components: **ExampleComponent1** and **ExampleComponent2**. Inside **ExampleComponent1**, we’re keeping track of the **count** state:

```javascript
import {useState} from "react"

export default function App() {
  return (
    <main>
      <ExampleComponent1 />
      <ExampleComponent2 />
    </main>
  )
}

export function ExampleComponent1 {
  const [count, setCount] = useState(0)
  return <div>Example component 1</div>
}

export function ExampleComponent2 {
  return <div>Example component 2</div>
}
```

Now let’s say we later on discover that **ExampleComponent2** will also need access to the **count** state.

What you typically do is lift the state up to the parent component. But oftentimes, in real-world scenarios, a lot of other components might need access to the same state.

Rather than manually passing down the props into the various components, the better approach would be to use the context API. Let’s bring it into our App component:

```javascript
import {useState, useContext} from "react"

const CountContext = useContext(null)

export default function App() {
  const [count, setCount] = useState(0)

  return (
    <main>
      <CountContext.Provider value={{count, setCount}}>
        <ExampleComponent1 />
        <ExampleComponent2 />
      </CountContext.Provider>
    </main>
  )
}

export function ExampleComponent1 {
  const [count, setCount] = useContext(CountContext)
  return <div>Example component 1</div>
}

export function ExampleComponent2 {
  return <div>Example component 2</div>
}
```

Basically, you’re to wrap the part of your app that needs access to the global state in the Context Provider component.

You’d then pass the variables you want the children components to access, which, in our case, is the **count** variable and **setCount()** method.

Now here comes the mistake.

Keep in mind that we’re consuming the context in ExampleComponent1, but not in ExampleComponent2. The idea is that, when we change the state in the parent App component, it’s only the components using the context that get re-rendered.

But the reality is that both components nested in the provider will be rerendered when the state changes.

To demonstrate this, let’s log different messages from both components and add a button in ExampleComponent1 that, when clicked, updates the state:

```javascript
// App component goes here

export function ExampleComponent1 {
  const [count, setCount] = useContext(CountContext)
  console.log("ExampleComponent1 rendering")

  return (
    <div>Example component 1 
      <button onClick={() => setCount(count + 1)}> Click me</button>
    </div>
  )
}

export function ExampleComponent2 {
  console.log("ExampleComponent2 rendering")

  return <div>Example component 2</div>
}
```

Because only the first component is using the context, the second should not be affected when we click the button to update the state, right?

Wrong!

When you open your browser’s console, refresh the page, and click the button, you’ll see the messages in both components being logged there. This means that both ExampleComponent1 and ExampleComponent2 have been re-rendered.

Now imagine that you have the provider high up in your component tree. The whole app will likely rerender every time you make a change. This is very inefficient and could result in significantly lowered performance in large applications.

## The Solution is to Put The React Context in a Separate File

The solution to this problem is quite straightforward.

You’d start by creating a new folder named **contexts** inside of the **src/** directory within your project’s directory structure.

Then inside the **src/contexts** directory, create a file named **count-contexts.jsx** and paste in the following code:

```javascript
import React from "react"

export const CountContext = useContext(null)

export default function CountContextProvider({children}) {
  const [count, setCount] = useState(0)
  
  return (
    <CountContext.Provider value={{count, setCount}}>
      {children}
    </CountContext.Provider>
  )
}
```

You’re basically moving the context provider, along with the global state, to a separate file. And this time, the provider component accepts the children from the parent.

Now in your App component, import the **CountContextProvider** and the CountContext variable from the newly created file, then use the former to wrap the children elements:

```javascript
import CountContextProvider, {CountContext} from './contexts/count-context'

export default function App() {
  const [count, setCount] = useState(0)

  return (
    <main>
      <CountContextProvider>
        <ExampleComponent1 />
        <ExampleComponent2 />
      </CountContextProvider>
    </main>
  )
}

// Other components
```

Now when you save the file, open your browser’s console, refresh the browser, and click the button, you’ll notice that only ExampleComponent1 is being re-rendered. And this is because it’s the only one using the context.

## Conclusion

If you’re building a large application with a lot of components requiring the same state, then it’s crucial that you use the technique we covered in this article to improve your app’s performance, and consequently, the user’s experience.

Want to collaborate with me? [Fill out this form](https://forms.gle/2h3UKhM55KZPNXm5A).