# Optimizing React Context and Re-renders

# React Context and Re-renders

In this post, we'll dive into how re-renders happen in the situation of React Context.

I want us to understand how it works and also how you can optimize if needed.

# Recap of using Context

Let's quickly look at some code to remind ourselves how Context is used:

```javascript
// DashboardContext.js
import { createContext, useContext } from 'react'

type DashboardContextType = {
  name: string
  age: number
}

const DashboardContext =
  createContext<DashboardContextType | null>(null)

type DashboardProviderType = {
  children: React.ReactNode
  name: string
  age: number
}

export const DashboardProvider = ({
  children,
  name,
  age
}: DashboardProviderType) => {
  return (
    <DashboardContext.Provider value={{ name, age }}>
      {children}
    </DashboardContext.Provider>
  )
}

export const useDashboardContext = () => {
  const context = useContext(DashboardContext)

  if (!context) {
    throw new Error('useDashboardContext must be used within an DashboardProvider')
  }

  return context
}
```

How to use the provider:

```javascript
// App.js

<DashboardProvider name="John" age={20}>
  <Dashboard />
</DashboardProvider>
```

The user values `name` and `age` are passed to the provider. They would in a real scenario e.g. be fetched from an API.

In `Dashboard`, we can use the `useDashboardContext` hook to access the values:

```jsx
// Dashboard.js

const { name, age } = useDashboardContext();
```

You can also have other components use the `useDashboardContext` hook to access the values. They would have to be inside the `DashboardProvider` component. So next to `Dashboard` component itself or inside of it.

# When will a component re-render?

Any component that consumes the context via `useContext` will re-render when the context changes.

If the component is inside the `DashboardProvider` but doesn't consume the context, it won't re-render when the context changes.

# Oops, we have an issue

Many components are consuming the context and re-render whenever the context changes.

We want to minimize the re-renders as it's getting expensive.

Let's go over things you can do in this situation.

# Evaluate what's causing the re-renders

Start by understanding the value of your context.

Pin down why re-renders are happening and when they're happening.

I recommend using the React DevTools to do this.

# Split the context

If you've have a single context with a dozen of values, you can look at which components need what specific value.

Some components are related and are supposed to re-render together while others are completely unrelated.

Split the context into multiple contexts if they have values that are unrelated.

A sign here is to see which components are using the same values from the context.

# Use the `useMemo` hook

You can use the `useMemo` hook to memoize the values of the context.

This can help when you've one Provider, but different states. Mind you, you'll have to create multiple consumption hooks if you take this approach.

For example:

```javascript
import React, { createContext, useContext, useState, useMemo } from "react";

const DashboardContext = createContext();

export function DashboardProvider({ children }) {
  const [userInfo, setUserInfo] = useState({
    name: "Alice",
    email: "alice@example.com",
  });
  const [systemMetrics, setSystemMetrics] = useState({ cpu: 50, network: 20 });

  const userInfoValue = useMemo(
    () => ({
      ...userInfo,
      setUserInfo,
    }),
    [userInfo]
  );

  const systemMetricsValue = useMemo(
    () => ({
      ...systemMetrics,
      setSystemMetrics,
    }),
    [systemMetrics]
  );

  return (
    <DashboardContext.Provider value={{ userInfoValue, systemMetricsValue }}>
      {children}
    </DashboardContext.Provider>
  );
}

export function useUserInfo() {
  const context = useContext(DashboardContext);
  return context.userInfoValue;
}

export function useSystemMetrics() {
  const context = useContext(DashboardContext);
  return context.systemMetricsValue;
}
```

Every component using `useUserInfo` would only re-render when the `userInfo` changes. Same goes for `useSystemMetrics`.

This is also called "selective rendering". Specific hooks to only return what the component needs. If other data changes outside of what the specific hooks return, the component doesn't re-render.

# State from above

If you've the issue where state comes from above the component that uses the Provider, you can use the `React.memo` function to memoize the component itself so that it doesn't re-render when its props changes.

This from my experience is rarely an issue, but worth mentioning:

```javascript
const ConsumerComponent = React.memo(function ({ value }) {
  // Component implementation
});
```

# `useMemo` on the value passed to Provider

You may be creating a new object every time the component re-renders, which causes the Provider to believe the value has changed, and then it re-renders all of its components.

Be careful, this also happens if you inline the Provider value.

```javascript
// Bad
value={{ name, age }}

// Good
const value = useMemo(() => ({ name, age }), [name, age]);
```

# Further read

If you want to learn more fancy stuff, you may want to read the docs about [Scaling Up with Reducer and Context](https://react.dev/learn/scaling-up-with-reducer-and-context).
