is useMemo required to manage state via the context API in reactjs?
Asked Answered
M

3

45

I am trying to understand this (mostly very helpful) article which describe how to use react's context API to manage application-level state. For a simple application (in this case a basic counter application), it uses the following solution:

const CountContext = React.createContext()

function CountProvider(props) {
  const [count, setCount] = React.useState(0)
  const value = React.useMemo(() => [count, setCount], [count])
  return <CountContext.Provider value={value} {...props} />
}

to provide the context, and then the following hook which can be used in a component somewhere down the component tree:

function useCount() {
  const context = React.useContext(CountContext)
  if (!context) {
    throw new Error(`useCount must be used within a CountProvider`)
  }
  return context
}

My Question:

I'm struggling to understand why the useMemo hook is needed here. There is no especially heavy computation involved here so I'm not sure why we're memoizing these values. Wouldn't this work just as well if the context provider looked as follows:

function CountProvider(props) {
  const [count, setCount] = React.useState(0)
  return <CountContext.Provider value={value} {...props} />
}

I feel like there is probably something that I'm missing!!

Marbles answered 6/6, 2020 at 10:43 Comment(1)
As a quick aside, I would remove the setCount from your dependency array as React guarantees this function will never update.Statesman
C
62

There is a very simple theory to why you should use useMemo to memoize the values passed to the Context Provider. When you pass the value to Context Provider, either as an object or an array like:

return <CountContext.Provider value={{state, setCount}} {...props} />

or

return <CountContext.Provider value={[state, setCount]} {...props} />

What essentially happens is that every time the CountProvider component re-renders a new reference to object or array is being passed as value to CountContext.Provider and hence even if the actual value may not have changed, the Context consumers are re-rendered since the reference check fails for the value.

Now you may or may not need a useMemo depending on what logic you have in your ContextProvider. For instance, in your case the CountContext is just using one state i.e count and passes it on to the child and if the CountContext is one of the top level element which is not re-rendered by anything other than the count change then in that case whether you use useMemo or not makes no difference since the reference of the returned value from useMemo is also updated on count change

However if you have certain parents to CountProvider which can cause CountProvider to re-render, useMemo to memoize context value comes in handy to avoid re-rendering of all the context consumers

Classis answered 6/6, 2020 at 11:44 Comment(2)
Thankyou. Yes, this makes perfect sense,Marbles
Glad to have helped :-)Classis
U
14

Calling setCount will always re-render a component

I believe that calling setCount will always rerender even if the same value is passed to the function.

But the line

const value = React.useMemo(() => [count, setCount], [count])

Will stop setCount being called unless there is a different value for the count. Thus reducing re-renders and increasing performance.

You can test this theory out by putting a log inside and seeing how the component renders with and without the useMemo.

Usable answered 6/6, 2020 at 11:32 Comment(4)
Thanks. yes, that makes sense. Both this answer and the other one give good reasons to memoize the context value. I've accepted the other answer, but they both seem to be valid reasons.Marbles
certainly, the other answer gives more depth. I'm just happy to help a fellow react enthusiast!!Usable
I have the same problem as the author and unfortunately this suggestion did not work for me :(Hilaire
const value = useMemo(() => ({ count, setCount}), [count]); first parameter should be object not arrayThoroughgoing
P
0

React.useMemo is never necessary because it's a performance optimalisation.

The idea that it will trigger unnecessary re-renders if you don't use useMemo is falsy. React only re-renders components after a state change somewhere up the component tree.

Here is proof that is doesn't cause unnecessary re-renders: https://codesandbox.io/s/stupefied-franklin-5jf5ke?file=/src/App.tsx

The only exception is when a component is wrapped in a React.memo(). In that case useMemo on the props or on the value passed to the Context.Provider can prevent unnecessary re-renders.

Is it a bad practice to wrap the value of the context in a useMemo? No I don't think it is, especially if you are using that context in the dependency array of other hooks or if you are providing it to a memoized component React.memo().

Pyosis answered 19/1, 2023 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.