React useContext re-render issue

xxlee (Ching Hung Lee)
2 min readJun 11, 2020

Context provides a convenient solution to maintain state in multi-level component. If you are building a component which is cross-project, or even published to npm, splitting with redux makes the component easily integrate with any project.

I used to write a useSomeContext hook like this, and will use this convention in following example:

// src/hooks/useSomeContextimport { useContext } from 'react';
import { SomeContext } from 'path/to/SomeContext';
export default function useSomeContext(){
return useContext(SomeContext);
};

Suppose there are name and count in our context:

{
name: 'apple',
count: 12,
}

And there is a component for displaying name with some cool style:

import useSomeContext from '@/src/hooks/useSomeContext';const SomeComponent = () => {
const { name } = useSomeContext();
...
}

Consider count changed for some reason. Though the name doesn’t change at all, but SomeComponent still re-rendered. If we write in redux, this issue can be easily handled by proper selector of useSelector :

// Bad, may cause re-render issue
const { count } = useSelector(state => state.someReducer);
// Good
const count = useSelector(state => state.someReducer.count);

There is no native selector helper for context now (v16.13.1 when I write this article). Though the RFC of context selector has been proposed (https://github.com/reactjs/rfcs/pull/119), but what if I want to use it now? Luckily, there is also an npm package provides same function.

With use-context-selector , first we have to replace React.createContext with createContext of this package:

// path/to/SomeContextimport { createContext } from 'use-context-selector';export const SomeContext = createContext();

In useSomeContext hook, we replace useContext with useContextSelector :

// src/hooks/useSomeContextimport { useContextSelector } from 'use-context-selector';
import { SomeContext } from 'path/to/SomeContext';
export default function useSomeContext(selector = v => v){
return useContextSelector(SomeContext, selector);
};

Note that I provide a default value for selector, which makes useSomeContext is still compatible with legacy code.

Now we can use context with a selector in component:

import useSomeContext from '@/src/hooks/useSomeContext';const SomeComponent = () => {
const name = useSomeContext(v => v.name);
...
}

Bravo! SomeComponent would no longer re-render when unrelated context value changed.

If you want to solve the re-rendering issue without installing an extra package, there are also some solutions provided on this Github issue thread:

--

--