React useMemo is not a classical memorization

Fang Jin
JavaScript in Plain English
4 min readAug 14, 2021

--

Why you shouldn’t confuse yourself between a useMemo and a memorization

Maybe this is just me, but for a long time, I wired myself between a useMemo and a classical memorization. But actually from the study of useMemo source code, I made peace that they are very different, at least they don’t have much in overlap. Maybe the only thing they share is a partial word “memo”.

Design and implementation

useMemo is very close to useEffect , believe it or not, in terms of the syntax, usage as well as the implementation. Maybe we can even simulate a useMemo behavior with a useEffect .

const Title = ({ text1, text2 }) => {
const [textB, setTextB] = useState("Hello")
useEffect(() => {
setTextB(text1 + "s")
}, [text1])
return (
<>
<ChildA />
<ChildB textB={textB} />
</>
)
}

In the preceding code, when text1 changes, it updates the textB which gets sent to ChildB component via prop textB .

If you ask me what is useMemo , I will say that’s the idea. It wants to skip a render for any irrelevant state or prop change, so the ChildB does not get unnecessary renders. In case the ChildB is a heavy component or text1 + "s" is a heavy operation, this is where you refine for a better performance.

You can argue textB isn’t worth a passive effect, which gets collected after all components rendered and flushed in an async way. Sure, let’s replace it with a useMemo which gets invoked under the same render of the component Title. Nice!

const Title = ({ text1, text2 }) => {
const textB = useMemo(() => {
return text1 + "s"
}, [text1])
return (
<>
<ChildA />
<ChildB textB={textB} />
</>
)
}

This simple interface and execution doesn’t change the fact that it’s only skipping renders and assigning values conditionally.

Memorization

Now let’s talk about what is a memorization, or something about caching.

Memoization is an optimization technique in computer programs, primarily designed to speed up the process by storing the results of expensive operations and returning the cached result if it has been computed under the same condition before.

Use a classical Fibonacci problem as an example. And recursive writing turns out to be costly so we use a cache to store past calculated values to reduce future calculations.

const fibs = { 0: 1, 1: 1 }function fib(n) {
if (!fibs[n]) {
fibs[n] = fibs[n - 1] + fibs[n - 2]
}
return fibs[n]
}

Say in the React app, we start with n=0 , unless n=1 we don’t want to calculate a new value. We could add a useMemo .

const Title = ({ n, text }) => {
const f = useMemo(() => fib(n), [n])

return <div>{text} - {f}</div>
}

The first thing we quickly find out is that it actually reuses the fib function, which means their functionalities don’t overlap at all. In another way useMemo doesn’t do what a fib does. Amazing?

Another finding is that what the Title does is to make sure when another prop text changes, it doesn’t calculate the f again. But if we change n from 3 to 2 , it still assigns from the useMemo . Which means the code can be reduced to the following.

const Title = ({ n, text }) => {
const f = fib(n)

return <div>{text} - {f}</div>
}

Haha, this is a bit funny now. We literally removed the useMemo . Why? Because the saved calculation is already implemented by fib , the classical memorization. And we can’t take advantage of useMemo simply because changing n to any other value is guaranteed a new render.

The question is what the useMemo used for here. Yes, nothing.

What is a useMemo

The useMemo has one memoized storage built for a previous value only. If you can take advantage of it to save you from some extra render, that’s great. Otherwise you just over-complicate things.

Don’t expect useMemo to give you more values than a previous value. React itself is a big state machine, given a change, it moves to a new state. All it cares is the future and current scene. Switching from a t-1 to t is all it cares. But remembering all t and switching from a t to t-2 is not going to fit in React context. Therefore useMemo can’t even begin to fit in the memorization context. If you really want to argue, we can say React uses a very specialized memorization for a previous state, or just call it a state machine.

The source code of useMemo can’t be any more compact and is attached here for reference. For the first time render, it calls mountMemo .

function mountMemo(nextCreate, deps) {
const hook = mountWorkInProgressHook()
const nextDeps = deps === undefined ? null : deps
const nextValue = nextCreate()
hook.memoizedState = [nextValue, nextDeps]
return nextValue
}

And for future updates, it calls updateMemo .

function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook()
const nextDeps = deps === undefined ? null : deps
const prevState = hook.memoizedState
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1]
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0]
}
}
}
const nextValue = nextCreate()
hook.memoizedState = [nextValue, nextDeps]
return nextValue
}

In a sense, useMemo is an advanced assignment statement, it gives us the ability to conditionally assign a value.

const a = 1
const a = useMemo(() => 1, []) // conditionally

Funny as it sounds, that’s all it does.

More content at plainenglish.io

--

--

#OpenToWork Front-end Engineer, book author of “Designing React Hooks the Right Way” sold at Amazon.