26 Jul 2021
A Visual Guide to React Rendering - Props
The Child component is wrapped in memo
. Its props don't change. So 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-like powers in controlling component rendering.
This article is the 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 have 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 for yourself. Paste "Alex" === "Alex"
into your browser console.
Non-primitives
The second type of value is non-primitive. Here we pass an object as a prop. The object is a non-primitive value. So why does a non-primitive value make the Child re-render?
Remember, to decide if the props have 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 this 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 a visualization of the reference comparison.
Although it seems that a
and b
are identical, each of them stores a reference to a different value. Similarly, {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 this, how do we prevent the Child from being re-rendered? The easiest solution is to declare the variable outside of the 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 this wouldn't work if we declared 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. If we want to prevent the Child from being re-rendered, we need to provide the same reference as the prop.
Memoization
In real-world scenarios, 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 the React component. It must be declared inside. But how do we prevent a variable from being redeclared and reassigned a 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".