The biggest pitfall of React Context - unwanted re-render

Usually, in small apps, people create a global context to share state across the application.

In the following example,We have a global context:

1import { createContext, useContext, useState } from 'react';
2
3type ContextType = {
4  stateOne: string;
5  stateTwo: string;
6  setStateOne: React.Dispatch<React.SetStateAction<string>>;
7  setStateTwo: React.Dispatch<React.SetStateAction<string>>;
8}
9
10const GlobalContext = createContext<ContextType>({
11 stateOne: '',
12 stateTwo: '',
13 setStateOne: () => null,
14 setStateTwo: () => null,
15});
16
17export const GloBalProvider:React.FC<{children: React.ReactNode}> = ({ children }) => {
18  const [stateOne, setStateOne] = useState('111');
19  const [stateTwo, setStateTwo] = useState('222');
20
21  return (
22    <GlobalContext.Provider
23      value={{
24        stateOne, 
25        setStateOne,
26        stateTwo,
27        setStateTwo
28      } }
29    >
30      {children}
31    </GlobalContext.Provider>
32  );
33};
34
35export const useGlobalContext = () => useContext(GlobalContext);

Using that context:

1import { useEffect } from 'react';
2import { GloBalProvider, useGlobalContext } from './GlobalContext';
3
4const ChildOne = () => {
5  const { stateOne, setStateOne } = useGlobalContext();
6  useEffect(() => {
7    console.log('TestContext ChildOne rerender');
8  });
9
10  return (
11    <>
12      <div>child one: {stateOne}</div>
13      <button onClick={() => setStateOne('abc')}>change stateOne</button>
14    </>
15  );
16};
17
18const ChildTwo = () => {
19  useGlobalContext();
20
21  useEffect(() => {
22    console.log('TestContext ChildTwo rerender');
23  });
24
25  return <span>child two</span>;
26};
27
28const TestContext = () => {
29  return (
30    <>
31      <GloBalProvider>
32        <ChildOne />
33        <br />
34        <ChildTwo />
35
36      </GloBalProvider>
37    </>
38  );
39};

let's run the code:

When we change stateOne, we can see that the ChildTwo component re-renders even though it doesn’t use stateOne at all.

Even with React.memo wrapping around ChildTwo, it still doesn’t work.

That’s the problem with Context — every time a state inside the context changes, it triggers a re-render of every component that consumes the context.

Moreover, putting everything inside a global context in a large application will create a mess that’s really hard to maintain.

Conclusion: in a large application with complex data and flows, we should use a state management tool like Redux instead