A Visual Guide to React Rendering - Props
The Child component is wrapped in memo
. Its props don’t change. Why does it still re-render when Parent renders?
There are two types of values in javascript. Understanding the differences between them gives you Jedi powers in controlling component rendering.
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.
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?
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.
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.
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.
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:
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:
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.
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:
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”