Can React useMemo be used to skip a child render?

Fang Jin
JavaScript in Plain English
5 min readAug 27, 2021

--

It can’t unless you know what is useMemo .

React provides quite an optimization engine out of the box where it applies bailout pathway whenever it sees fit. And most of the pathway are built behind the scene, and developer knows them by mouth. Sometimes we get wrong idea about using certain features.

What is useMemo?

The word “memo” gives us a sense that there’re some memorization scheme used. But on the contrary, if you were calling an assignment a memo, then programming itself is a giant memo engine, which defeats the purpose of using this word. That way, React could be called a giant memo as well, like a fiberMemoizedReact, whatever :)

A joke aside, useMemo isn’t related to any classical memorization and in fact it’s for skipping an assignment only. (Check article, React useMemo is not a classical memorization) If you have to call it a memo, it stores the previous value. Damn isn’t that what any variable does?

const a = useMemo(() => { return ... }, [a, b])

A useMemo is a single assignment statement replacement, and only performs the assignment when a or b changes. So it can be used to skip some assignment otherwise.

Bailout a child, failed

Let’s see how this is believed that it can skip a child render.

const Title = () => { 
const a = useMemo(() => {
return "Hello World"
}, [])
return <Child a={a} />
}

A variable a is created after the mount, and stores a primitive string value “Hello World” afterwards, so from the perspective of Child, since a doesn’t change any more, thus the Child is bailed out all the time.

Is this true? It can be only tested out if we render the parent Title, can’t it?

const Title = () => {
const [count, dispatch] = useState(0)
const onClick = () => { dispatch(v => v + 1) }
const a = useMemo(() => {
return "Hello World"
}, [])
return (
<>
<Child a={a} />
<div onClick={onClick}>{count}</div>
</>
)
}

We add a new state count and dispatch so that we can trigger a render after we click on the number.

Now the example is completed, do you really think the Child can bailout?

Child renders without a bailout with useMemo

The above Profiler figure confirms the Child renders as well as everything underneath it. You might think this is buggy, however this is by default how React is designed to do. Not only that, React does this quite consistently.

What happens is that, when the Title updates after the render, it reconciles all the children elements, therefore creating a new pair of props for the Child. This triggers the Child to render irregardless if the individual prop a is changed or not. (Check article, How does React render based on changes?)

Then why do people use useMemo for? Haha, it’s used for skipping assignment, since certain assignment is heavy. In our example we only returns a “Hello World”, but it could be a search function that can take as long as 500ms to finish. We can save quite some time if we can limit the assignment, similar idea to throttle, but based on the dependency change.

Bailout a child, success

Does that mean, we can’t use useMemo to bailout a child? There’re ways, but just not through a prop. Keep in mind what useMemo is designed to do, skip assignment of a value that can be in any format.

Let’s amend the bailout code a little bit.

const Title = () => {
...
const child = useMemo(() => {
return <Child a={"Hello World"} />
}, [])
return (
<>
<div onClick={onClick}>{count}</div>
{child}
</>
)
}

In above code, we create a new variable child. Instead of skipping an assignment for a prop, it skips an assignment for a render.

What is {child} ?

A prop, no.

An element, yes.

An element inside a children props of the parent <>, yes.

Imagine the children of <> is an array of elements, the first one being a div and second one being a Child . The above code makes the second element keeps the same reference of the render after the mount.

Child bails out with useMemo

Now you can see the Child does bailout, indicated by the light shaded area as well as every fiber below it. You can amend the Child to take more props to make it more practical.

  const [d, ] = useState("Hello World")  const child = useMemo(() => {
return <Child a={d} />
}, [d])

This is the strength of this approach. The control whether we can bailout a child comes from the parent’s decision. For a functional component it can be too late to bailout once a Child starts to render, as in React.memo.

Summary

useMemo in general can’t bailout anything, it only skips assignment of a value. So in order to use it for skipping a render, it needs to be applied to the render elements instead of props.

Appendix

It seems possible to make a component Memo to simulate the bailout depending on a dependency array. Memo is made by a React.memo based on the understanding of useMemo .

const Memo = React.memo(({ children, value }) => {
return children
}, (prev, next) => prev.value === next.value)

Now we can use it this way,

const Title = () => {
...
return (
<Memo value={a}>
<Child a={a} />
</Memo>
)
}

If a changes, the Child renders, otherwise it bails out the Child.

Child bails out with Memo component

The funny thing is that the element inside the dependency, such as a, will show up multiple times. But this is understandable, since one is specified at the parent, and one is what the child needs.

memoChild

If this approach works, we can generalize it in a higher order component to skip any change from children prop while responding to all other props.

import { memo } from 'react'const equalWithoutChildren = (prev, next) => {
for (let k in prev) {
if (k === 'children') continue
if (prev[k] !== next[k]) return false
}
return true
}
const memoChild = (Component) => {
const Memo = memo(({ children, ...props }) => {
return <Component children={children} {...props} />
}, equalWithoutChildren)
return Memo
}
export default memoChild

Then we can use it.

const Child_ = memoChild(Child)const Title = () => {
return (
<Child_ a={a}>
<Others b={b} />
</Child_>
)
}

So now if a changes, the Child component gets a render, independent to b or Others.

More content at plainenglish.io

--

--

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