React useContext re-render issue
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: