React 16 Context Unwanted Rerender

Murat Güney
3 min readMay 24, 2021

--

Since React 16.8 came into our lives, everything has changed a lot. With the concepts, we call “Hook”, writing components become much easier and more understandable now.

Now, creating contexts and accessing it very easy, and we don’t have to use “Redux” anymore. We can keep our user’s data in a context and get it in “1” line of code.

But, there is a very unexpected problem if you need to increase your application’s performance. If you access a context to get a simple function from it, even if you used the “useCallback” hook, the components that you used the “useContext” would rerender even the function doesn’t change!

It says basically if a state is changed in a context and even if you don’t use that value, but you called it in a component, that component will rerender no matter what! Let's take an example to see the problem clearly.

We created a simple counter context and its provider. “Increase” and “Decrease” functions are created by the “useCallback” hook, which prevents these functions recreate on each render. So basically, if a component only uses these functions, the component shouldn’t rerender.

With the “useContext” hook, we can access contexts easily. Now let's make a component that uses this context.

We made three components that use the “CounterContext” we just made. If you remembered we created our “Increase” and “Decrease” functions with the “useCallback” hook. So what is expected here is that “IncreaseButton” and “DecreaseButton” don’t rerender. But if you try, you will see that they rerender!

This problem seems not a big deal in this example, but if you call the “useContext” hook somewhere at the top of your app, even if you call only a variable that is not changed in the context, your application will rerender, and all of its children. And this will slow down your app.

There are 2 common solutions for that. I highly recommend the first one!

The first solution is “Rect.memo”! “memo” makes sure your component rerender, only if a prop is changed. But you need to move the children of the component which calls the “useContext”, to a new component which is created with “memo” and send the only necessary props.

In the example above, while “IncreaseButton” and “DecreaseButton” are rerendering, “IncreaseButtonMemo” and “DecreaseButtonMemo” won’t rerender! Because we only passed “increase” and “decrease” functions and they actually created with the “useCallback” hook. They only rerender if “increase” and “decrease” functions are recreated by the “useCallback” hook.

The second solution is the “useMemo” hook. You can wrap the return value of the component which calls the “useContext” hook with “useMemo”

This is another solution, but I don’t recommend this because in “useMemo” you can’t use another hook. And If the return value uses lots of variables, you need to pass all of it to the “dependency array” of the “useMemo” hook. And I think this way is not readable and understandable.

* BONUS SOLUTION

If you want to use an easy way, you can use

@reactivers/context-binder

It does the same thing without an extra component. And it provides selecting specific values from a context!

The first parameter is context, the second one is “selector function” and the third one is your component. You can get the return value of the “selector function” with the “context” key from props of the component.

npm install --save @reactivers/context-binder

You can find a full example in Github

And NPM

--

--