React tips — Context API (performance considerations)

Photo by Markus Spiske on Unsplash

The new Context API is really interesting and help us deal with prop-drilling and make the use of render props pattern more clear, but it comes with some “performance problems” due the unnecessary re-rendering. In fact, it isn’t a problem because the API comes with a way to work with this unnecessary re-rendering.

Before start working with observedBits and calculateChangedBits, let’s write a simple component using the context API:

$ create-react-app react-observedbits

Add a new folder called components and create a new file called stateCounterProvider.jsx

In this file I just created a empty context and a provider that just update the state every second. I also exported the Consumer to later use.

Now let’s create a new file called withState.jsx. It’s a high order component that add a consumer provider to any wrapped component passed in.

Basically I return a new component wrapped on Consumer passing the state and other props I wanted pass when use this HoC.

Now let’s create a new component called counter.jsx. It’s basically a consumer using our HoC:

Lastly but not least, let’s change the App.js file to wrap it all:

Here we are using the context provider (component StateCounterProvider) passing the initialState and using 3 consumers. The result should be this:

Great! But now let’s talk about performance. As you can see, every second all three child components (consumers) will be re-rendered. This is the default behavior using Context API, all child will be re-rendered once the provider state changes. In order to avoid this we can use observedBits.

Basically we need to add a calculateChangedBits function as second argument for createContext:

const { Consumer, Provider } = React.createContext(null, calculateChangedBits);

So, before re-render the consumers, React will compare each observedBits from consumer with some calculations inside this function. It seems confuse, so let’s change our code to use it:

First, let’s change the App.js and add observedBits to the consumers:

<StateCounterProvider initialState={{ value: 0 }}>
<Counter label="Current Value" observedBits={11} />
<Counter label="Last Odd Value" observedBits={1} />
<Counter label="Last Even Value" observedBits={10} /></StateCounterProvider>
Remember, they are binaries.

This change above tell to React that each of these consumers are different and should be re-rendered only if the pattern inside calculateChangedBits matches. So, let’s create the calculateChangedBits function. Go to stateCounterProvider.jsx file and change the context creation:

export const { Consumer, Provider } = React.createContext(
null,
(oldProps, newProps) => {
return newProps.value % 2 === 0 ? 10 : 1;
}

);

The above function will return 1 or 10 for even or odd number, so if you look at consumers, you can see two of them have observedBits 1 and 10, but the third one has 11, therefore it will be always be re-rendered. Ok, the last change will be on the withState.jsx file:

I just added the observedBits on params and used it on Consumer by adding unstable_observedBits={observedBits}.

Now, if you refresh the page you can see that the first consumer will be re-rendered in every second, the second will be re-rendered only when it’s Odd and the third when it was Even.

You can see the full example in my github: https://github.com/lblima/react-observedbits

That’s all folks,

Thanks for reading!