Fixing useContext Performance Issues
Tags: bluecollarcoder, reacttracked, statemanagement, react
Published: 2021-01-16 15:47:32

---
title: Fixing useContext Performance Issues
published: true
date: 2021-01-16 01:30:59 UTC
tags: bluecollarcoder,reacttracked,statemanagement,react
canonical_url: 
---

So you want to use `useState` and `useContext` as a state management solution, but every time the value of the context provider changes the entire tree gets re-rendered. You could use a library like [Recoil](https://recoiljs.org/), [Valtio](https://github.com/pmndrs/valtio), [Zustand](https://zustand.surge.sh/), and [Jotai](https://github.com/pmndrs/jotai) to get around this problem, but you’d have to change how you store and access global state.

Is there a way to just fix the issues with `useContext`? Glad you asked! Yes there is! It’s [react-tracked](https://react-tracked.js.org/), a new library from Daishi Kato, who has been doing amazing work in the React state management space.

![](https://cdn-images-1.medium.com/max/1024/1*Oce5M2lgmhM_50gF36guhA.jpeg)<figcaption>Banner image</figcaption>

### Setting up Your useState/useContext Global Store

The first thing you need to do is set up your store properly. Daishi has [some excellent documentation on this](https://react-tracked.js.org/docs/quick-start) already, but let’s walk through the Typescript version of the store step by step.

First we create a `store.tsx` file and start that file with some React imports, as well as the structure of the store and a function that creates the useState hook.

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

const initialState = {
  text1: "text1",
  text2: "hello",
};

const useMyState = () => useState(initialState);
```

Our initial store is pretty simple, we have a couple of pieces of text, and we have a function that invokes the React useState hook with that initial state.

Why don’t we just call `useState` right there and cache the result? Because React hooks need to be called from within a React component so they can be bound to a component instance. Thus we need a function that will create the state when we need it.

The next step is to create the context:

```ts
const MyContext = createContext<ReturnType<typeof useMyState> | null>(null);
```

This is a standard `createContext` call where the context will either hold `null` (at startup) or the return type from the `useMyState` call. Which will be the standard useState return of an array with the current value, and a setter function.

After that we need to create the `SharedStateProvider` React functional component:

```ts
const MyContext = createContext<ReturnType<typeof useMyState> | null>(null);

export const SharedStateProvider: React.FC = ({ children }) => (
   <MyContext.Provider value={useMyState()}>
      {children}
   </MyContext.Provider>
);
```

This component goes at the top of the React tree and provides the context down to any child components that way to consume it. Notice that we are invoking `useMyState` at this time because we are in the context of the React component and it’s safe to do so.

And our final step is to create a custom hook that gets the state and the state setter:

```ts
export const useSharedState = () => {
   const value = useContext(MyContext);
   if (value === null)
     throw new Error('Please add SharedStateProvider');
   return value;
};
```

This custom hook first uses `useContext` to get the context. It then checks to make sure it has that context and throws an error if it doesn’t. And then finally it returns the context, which would be the output of `useState` , so an array with a value and a setter.

Now our global store setup is done. No libraries. Just basic React with hooks and structured in a really clean way.

### Using the Store

Now that we have our store defined we first import the `SharedStateProvider` and add it to our `App` like so:

```ts
import { SharedStateProvider } from "./store";

const App = () => (
  <SharedStateProvider>
     ...
  </SharedStateProvider>
);
```

This will not only provide the context down to any component that wants to consume it, but also initialize the state to the value in `initialState`.

Finally we could add some components that use that state, like so:

```ts
import { useSharedState} from "./store";

const Input1 = () => {
  const [state, setState] = useSharedState();
  return (
    <input
      value={state.text1}
      onChange={(evt) =>
        setState({
          ...state,
          text1: evt.target.value,
        })
      }
    />
  );
};

const Text1 = () => {
  const [state] = useSharedState();
  return (
    <div>
      {state.text1}
      <br />
      {Math.random()}
    </div>
  );
};

const Text2 = () => {
  const [state] = useSharedState();
  return (
    <div>
      {state.text2}
      <br />
      {Math.random()}
    </div>
  );
};
```

Now this code will work just fine. But you’ll notice that the `Text2` component, which will never need to be updated because we have no way to update the `text2` value it’s looking at will get updated any time the global state changes.

This is because React has no way to track what parts of the state the components are looking at. It doesn’t do that work for you, and that ends up being a performance problem when you have a lot of global state. Even the most minor change will end up re-rendering a bunch of components that don’t need re-rendering.

You can see that in this example because the random number on `Text2` will keep changing when you type characters into `Input1`.

![](https://cdn-images-1.medium.com/max/800/1*Z3kn-JBYZQHSRYLUrfDAfw.gif)<figcaption>GIF showing errant re-rendering of the Text2 component</figcaption>

As you can see above, I’m not changing text2 and yet the component showing the `text2` value is re-rendering.

### React-Tracked to the Rescue

To fix this we bring in the 5Kb `react-tracked` library by adding it to our application:

```
npm install react-tracked
```

And from there we go back to the `store.tsx` file and import the `createContainer` function from the library:

```
import { createContainer } from "react-tracked";
```

We then **remove** the definitions for `useSharedState` and `SharedStateProvider` and add the following code:

```ts
export const {
  Provider: SharedStateProvider,
  useTracked: useSharedState,
} = createContainer(useMyState);
```

The `createContainer` function takes the state creation function:

```ts
const useMyState = () => useState(initialState);
```

And it then returns a `Provider` and a `useTracked` which are remapped on export to `SharedStateProvider` and `useSharedState` which is what the components are expecting.

The result is that an isolation where components only re-render if the data they are "tracking" is changed, this is shown below:

![](https://cdn-images-1.medium.com/max/800/1*wjHXQQOBnqku-8htRuoF3w.gif)<figcaption>GIF showing only the Text1 component rendering when text1 is changed</figcaption>

Now when I change `text1` only the `Text1` component changes.

Not bad for just five 5Kb of additional code.

### Conclusion

Daishi Kato’s `react-tracked` library is an easy way to take a well factored `useState/useContext` state management solution and make it performant by intelligently tracking which parts of the state are used by each component.

### Video Version

Check out this Blue Collar Code Short Take on react-tracked if you want a video version of this article.

{% youtube 6yBv-_COJkk %}

* * *