Alex Sidorenko

A Visual Guide to React Rendering - Props

July 26, 2021

The Child component is wrapped in memo. Its props don’t change. Why does it still re-render when Parent renders?

Two components Parent and Child. Parent passes a prop {display: 'flex'} to the Child. Child re-renders every time parent renders

There are two types of values in javascript. Understanding the differences between them gives you Jedi powers in controlling component rendering.

Yoda

This article is a second chapter of "A Visual Guide To React Rendering". If you haven't yet, check out the first chapter It always re-renders.

Primitives

The first type of value is primitive. Let’s try to pass a primitive value as a prop to the Child.

Two components Parent and Child. Parent passes a prop name='Alex' to the Child. Child doesn't re-render when parent renders

The Child is wrapped in memo. This means it will only re-render when its props change. The way memo determines if props changed is by shallow comparison prevProp === nextProp. Since "Alex" is a string, which is a primitive value, this comparison will return true, and the component won’t re-render. You can check it by yourself, paste this in your browser console "Alex" === "Alex".

Non-primitives

The second type of value is non-primitive. Here we pass the object as a prop. The object is a non-primitive value. So why does a non-primitive value makes the Child re-render?

Two components Parent and Child. Parent passes a prop {display: 'flex'} to the Child. Child re-renders every time parent renders

Remember, to decide if the props changed, memo does the shallow comparison. When you compare two non-primitives like that {display: "flex"} === {display: "flex"}, the result will always be false. You can check it in your browser console.

In JavaScript, objects are a reference type. Two distinct objects are never equal, even if they have the same properties. Only comparing the same object reference with itself yields true.

MDN - Comparing Objects

References

When we say a variable stores a reference, we mean that it points to some value in memory. Here’s the visualization of the reference comparison.

a = {display: 'flex'}; b = {display: 'flex'}; a === b // false; a === a // true

Although it seems that a and b identical, each of them stores a reference to a different value. The same way {display: "flex"} === {display: "flex"} compares two different values in memory.

Check out Kyle Simpson’s You Don’t Know JS (Values vs References) for more info.

Knowing all that, how do we prevent the Child from getting re-rendered? The easiest solution is to declare the variable outside of React component and pass it as a prop.

Two components Parent and Child. A variable declared outside of component: const style = {display: 'flex'}. Parent passes a prop style to the Child. Child doesn't re-render when parent renders

This way, when memo compares props, it will do style === style // true instead of {display: "flex"} === {display: "flex"} // false

Notice that it wouldn’t work if we declare the variable inside the component:

Two components Parent and Child. A variable declared inside Parent component: const style = {display: 'flex'}. Parent passes a prop style to the Child. Child re-renders every time parent renders

That’s because every time Parent renders, the style variable gets redeclared with a new reference pointing to a new value.

Anonymous functions

There are other non-primitive values like arrays and functions. It’s a common pattern in React to pass an anonymous function to an event handler like this:

Two components Parent and Child. Parent passes an anonymous function to the Child. Child re-renders every time parent renders

It’s important to understand that since the function is a non-primitive value, the same rules of comparison apply. And if we want to prevent the Child from getting re-rendered, we need to provide the same reference as the prop.

Two components Parent and Child. A variable declared inside Parent component: const handler = () => console.log('Click'). Parent passes a prop handler to the Child. Child doesn't re-render when parent renders

Memoization

In real-world, most of the time, when you deal with non-primitive values passed as props, they rely on the state or other props of a component:

Two components Parent and Child. Parent passes an anonymous function as a prop the Child. This function updates Parent's state. Child re-renders every time parent renders

In this case, it’s impossible to declare a variable outside of React component. It must be declared inside. But how do we prevent a variable from being redeclared and reassigned new reference on every re-render? That’s why React has useMemo and useCallback hooks to memoize props. But this is a topic for the next part of “A Visual Guide To React Rendering”

Next chapter

A Visual Giude to React Rendering - useMemo

Want to get better at modern React?

Subscribe to get one short article delivered to your inbox every week

One article a week. No spam.
Unsubscribe any time