Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useMemo vs useState for React hooks constants

Defining a calculated (initialized) constant using React hooks can be performed in two ways that seem functionally equivalent. I don't want to get into the use cases for this, but suffice to say that there are cases where a constant value can be derived from initial props or state that isn't expected to change (think route data, bound dispatch, etc).

First, useState

const [calculatedConstant] = useState(calculateConstantFactory);

Second, useMemo

const calculatedConstant = useMemo(calculateConstantFactory, []);

Both of these seem functionally equivalent, but without reading the source code, I'm not sure which is better in terms of performance or other considerations.

Has anyone done the leg work on this? Which would you use and why?

Also, I know some people will recoil at the assumption that state can be "considered constant". I'm not sure what to tell you there. But even without state, I may want to define a constant within a component that has no state at all, for example, creating a block of JSX that doesn't change.

I could define this outside of the component, but then it's consuming memory, even when the component in question is not instantiated anywhere in the app. To fix this, I would have to create a memoization function and then manually release the internal memoized state. That's an awful lot of hassle for something hooks give us for free.

Edit: Added examples of the approaches talked about in this discussion. https://codesandbox.io/s/cranky-platform-2b15l

like image 806
bfricka Avatar asked Aug 26 '19 20:08

bfricka


People also ask

Why React hook useState uses const and not let?

useState is simply a state updating function. Const is used here because the change of value is being managed somewhere else by React. You're telling React to manage some value for you by calling useState.

When would you use useMemo React hook?

The React useMemo Hook returns a memoized value. Think of memoization as caching a value so that it does not need to be recalculated. The useMemo Hook only runs when one of its dependencies update.

Why do we use const for useState?

What does useState return? It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState() . This is similar to this.state.count and this.setState in a class, except you get them in a pair.

When should I use useMemo?

Here are a couple of cases when you should consider using useMemo: You're noticing a component's render is frustratingly slow, and you're passing a calculation to an unknowable number of children, such as when rendering children using Array. map()


1 Answers

You may rely on useMemo as a performance optimization, not as a semantic guarantee

Meaning, semantically useMemo is not the correct approach; you are using it for the wrong reason. So even though it works as intended now, you are using it incorrectly and it could lead to unpredictable behavior in the future.

useState is only the correct choice if you don't want your rendering to be blocked while the value is being computed.

If the value isn't needed in the first render of the component, you could use useRef and useEffect together:

const calculatedConstant = useRef(null);

useEffect(() => {
  calculatedConstant.current = calculateConstantFactory()
}, [])

// use the value in calcaulatedConstant.current

This is the same as initializing an instance field in componentDidMount. And it doesn't block your layout / paint while the factory function is being run. Performance-wise, I doubt any benchmark would show a significant difference.

The problem is that after initializing the ref, the component won't update to reflect this value (which is the whole purpose of a ref).

If you absolutely need the value to be used on the component's first render, you can do this:

const calculatedConstant = useRef(null);

if (!calculatedConstant.current) {
  calculatedConstant.current = calculateConstantFactory();
}
// use the value in calculatedConstant.current;

This one will block your component from rendering before the value is set up.

If you don't want rendering to be blocked, you need useState together with useEffect:

const [calculated, setCalculated] = useState();

useEffect(() => {
  setCalculated(calculateConstantFactory())
}, [])

// use the value in calculated

Basically if you need the component to re-render itself: use state. If that's not necessary, use a ref.

like image 73
Raicuparta Avatar answered Nov 16 '22 02:11

Raicuparta