Compute values on mount with React Hooks: State vs Memo vs Ref

Ricardo Lopes
Aug 27 · 4 min read

I recently came across this question:

I have a functional React component that computes a value when the component is mounted. After mounting, this value is never updated. Which is the better approach?

const value = useMemo(() => computeValue(), [])

or

const [value] = useState(() => computeValue())

?

And the answer is that they both work, but neither is ideal. Let’s see why.

useMemo

At a first glance, useMemo might seem perfect for this. It only recomputes the value if the list of dependencies (second argument) changes. With an empty array as the dependency list, the value will only be computed on the first render. And it works. But here is the problem:

You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to "forget" some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo - and then add it to optimize performance.

So we can’t rely on useMemo for making sure a value is only computed once. Even if it works fine now, we shouldn't assume the behavior will be the same moving forward.

So what can we rely on?

useState

This one is closer to the correct answer, and it actually kinda works. But it is semantically incorrect.

When we pass the computeValue function as an argument to useState, it is used for lazy initialization. The result is that the value will be computed, but only on the first render. Seems like what we're looking for.

The problem is that this will block the first render until our computeValue function is done. The value will then never be updated again. So is this really a good use for component state? Let's think, what is the purpose of state in a React component?

We need state when we want the component to be able to update itself. Is that the case here? There is only ever one possible value during the component’s lifetime, so no. We are using state for something other than its purpose.

So if not in the state, where do we store our computed value?

useRef

Before Hooks, you might think of refs as something you use to access a child component and focus an input. But useRef is much simpler than that:

Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

useRef() is useful for more than the ref attribute. It's handy for keeping any mutable value around similar to how you'd use instance fields in classes.

Mutating the .current property doesn’t cause a re-render.

How is this useful here?

We initialize our ref with null. Then, we immediately update value.current to the computed value, but only if it hasn't been defined already.

The resulting behavior is identical to the previous example with useState. The render is blocked while the value is being computed. After that, the value is immediately available to be used on the first render.

The difference is just in the implementation: we’re not adding unnecessary state to the component. We are instead using a ref for its original purpose: keeping a value that persists between renders.

But what if we don’t want to block rendering while the value is being computed?

useState and useEffect

This solution will be more familiar to anyone who has tried React Hooks, as it is the standard way to do anything on component mount:

useEffect will run after the first render, whenever the dependency list changes. In this case, we set the dependency list to [], an empty array. This will make the effect run only after the first render, and never again. It does not block any renders.

Our first render will run with value = null, while the value is being computed. As soon as the computation is done, setValue will be called, and a re-render is triggered with the new value in place. The effect won't run again unless the component is re-mounted.

And this time it makes sense to have state, because there are two states the component can be in: before and after computing the value. This also comes with a bonus: we can show a “Loading…” message while the value is cooking.

Conclusion: State vs Ref vs Memo

The main lesson here is the difference between these:

useState:

  • for storing values that persist across renders;
  • updates trigger a re-render;
  • updates via setter function.

useRef:

  • also for storing values that persist across renders;
  • updates don’t trigger a re-render;
  • mutable directly via the .current property.

useMemo:

  • for performance optimizations only

Originally published at DEV.to.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade