This article was originally posted on my blog, please note the source when reprinted.

Many people use React Context as some kind of built-in Redux. Jack, for one, merges all the global state into one large object, gets a ‘single data source’, and stuffs it into the provider. He then finds the child component, calls useContext, and picks the desired properties. Everything seemed perfect, until one day he found his app too slow to use.

A bad example

Consider the following code, which may be the worst practice for React Context.

const Ctx = React.createContext();

const SideMenu = (a)= > {
  const { setHideSideMenu, hideSideMenu } = useContext(Ctx);
  return( <aside> <Menu hide={hideSideMenu} /> <button onClick={() => setHideSideMenu(x => ! x)}>toggle</button> </aside> ); }; const UserDashBoard = () => { const { user, setUser } = useContext(Ctx); React.useEffect(() => { fetchUser().then((data) => setUser(data.user)); } []); return <User username={user} /> }; const App = () => { const [user, setUser] = React.useState(''); const [hideSideMenu, setHideSideMenu] = React.useState(false); const [clock, setClock] = React.useState(Date.now()); React.useEffect(() => { const interval = setInterval(() => { setClock(Date.now()) }, 1000) return () => { clearInterval(interval); }} []); return ( <Ctx.Provider value={{ user, setUser, hideSideMenu, setHideSideMenu, }}> <Clock time={clock} /> <SideMenu /> <UserDashBoard /> </Ctx.Provider> ); }Copy the code

Make Context updates controllable

The first problem here is that the consumer of each context receives an update notification every second.

The clock state causes the App component to be updated so that a new ctx. Provider value is created. (If you can’t understand this behavior, maybe my previous blog post will help you.)

So if you need to treat objects or arrays as context values, use something like useMemo or useReducer to avoid unnecessary creation.

const App = () => { const [user, setUser] = React.useState(''); const [hideSideMenu, setHideSideMenu] = React.useState(false); const ctx = React.useMemo(() => ({ user, setUser, hideSideMenu, setHideSideMenu, }), [user, hideSideMenu]); . return <Ctx.Provider value={ctx}>... </Ctx.Provider>; }Copy the code

Remember your choices

Child components may use only a portion of the context’s values, whereas the context’s values are updated as a whole. If your build is expensive to re-render, it’s probably a good idea to remember the values you chose.

For example, if we want to remember the selection of the SideMenu component, we have two options:

  1. Split the component into two and call the internal componentmemo.
const SideMenuInner = React.memo(({ setHideSideMenu, hideSideMenu }) = > {
  return( <aside> <Menu hide={hideSideMenu} /> <button onClick={() => setHideSideMenu(x => ! x)}>toggle</button> </aside> ); }); const SideMenu = () => { const { setHideSideMenu, hideSideMenu } = React.useContext(Ctx); return ( <SideMenuInner setHideSideMenu={setHideSideMenu} hideSideMenu={hideSideMenu} /> ); };Copy the code

We can abstract out a HOC to do this:

const ConsumeWithSelector = (Component, context, selector) = > {
  const ctx = selector(React.useContext(context));
  return React.memo(props= ><Component {{ ... props, ... ctx }} />); }Copy the code
  1. Used in componentsuseMemoMethods.
const SideMenu = (a)= > {
  const { setHideSideMenu, hideSideMenu } = useContext(Ctx);
  return React.useMemo((a)= > (
    <aside>
      <Menu hide={hideSideMenu} />
      <button onClick={()= >setHideSideMenu(x => ! x)}>toggle</button>
    </aside>
  ), [hideSideMenu, setHideSideMenu]);
};
Copy the code

Break up the Context

We should not try to build something like a “single data tree” when using context, it will make the application very difficult to optimize. For most scenarios, the context can be broken up into multiple contexts by responsibility.

For example, in the previous example, we could split context into HideSideMenuCtx and UserCtx, and even HideSideMenuState, HideSideMenuSetter, UserState, and UserSetter.

const App = (a)= > {
  const [user, setUser] = React.useState(' '); . return ( <UserState.Provider value={user}> <UserSetter.Provider value={setUser}> ... </UserSetter.Provider> </UserState.Provider> ); }Copy the code

Too much fragmentation, however, can make the application difficult to maintain. There is no silver bullet in it. We should make trade-offs according to our own scenarios. However, we at least need to be able to anticipate how different strategies will behave.