Skip to main content

Groundwork

These docs discuss a lot of topics that revolve around the concept of referential equality. It is important to understand exactly how Javascript will compare two values because it is a foundational concept that plays a huge part in a lot of the upcoming sections.

note

In Javascript arrays are objects, as can be seen when you print the result of typeof []. In these docs the term object will often refer to any non-primitive data type i.e. {} and [].

Equality in Javascript

To properly explain this topic there are a few key concepts that must be understood, the first of these topics is how Javascript compares values. Because most people reading this are likely all over equality comparisons and operators, this is a more high level overview of comparing values vs. references and how that applies to React props comparisons.

When a value is compared for strict equality === Javascript will compare the two values without implicitly converting the type.

const num = 5;
const numStr = "5";

console.log(num === num); // true - both side of the operator is a number with the value 5.
console.log(num === numStr); // false - one value is the number 5, the other is a string with the character 5.

This is fairly obvious to most people, and is simple to grok in the context of React dependencies. More importantly though is how non-primitive types such as arrays and objects behave, because Javascript compares them by reference not by value. This means it looks at whether or not both values point to the same object instance, regardless of the values inside the object.

const obj1 = { a: "a", b: "b" };
const obj2 = { a: "a", b: "b" };

const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];

console.log(obj1 === obj1); // true - both objects have the same object instance.
console.log(arr1 === arr1); // true - both arrays have the same object instance.

console.log(obj1 === obj2); // false - both objects have a different object instance despite having the same values.
console.log(arr1 === arr2); // false - both arrays have a different object instance despite having the same values.

It is possible to compare value equality of objects, but that requires stepping through the object or array and comparing each internal value. Something like lodash's isEqual method is a common way to do that.

Components vs Pure and Memoised Components

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

React docs reference

React offers ways to memoise components with React.PureComponent for class components, and React.memo for function components. Both of these tell React to compare the current props with the new props, and if they have the same value skip the re-render. It does this with a shallow equality comparison on each prop.

In React Native, Flatlist, SectionList and VirtualizedLists are the only React.PureComponents, while the rest are React.Components. By wrapping your custom component in React.memo, we are memoising our own component in the same way. You can also view the source code of any React Native component on GitHub to check if they are extended by React.Component or React.PureComponent. See the source code for React Native lists here.

If you are not working with a Pure(Memoised)Component, then your component will re-render regardless of whether or not its props are memoised. However, please read the section Memoising Values as there are still situations where memoising should be implemented when dealing with non-memoised components.

This topic will be covered more in depth in the section on memoising components.

How memoisation applies in React

React on a conceptual level, frees developers from the burden of managing state reactivity, component lifecycle, and triggering updates. It does this via component dependencies on props, which it uses to determine if something needs to update or not.

By default, a React component will re-render whenever its parent re-renders, regardless of the value of its props. This continues on down the tree of components until it reaches the end.

When a React component is memoised through the use of React.memo, its default behaviour is to perform a shallow equality comparison, which compares the values of the current props to the next prop values. If the value of those props hasn't changed it skips the re-render, if their value has changed it triggers a re-render. This cascades down the tree in that a memoised component that skips a re-render will end that cascade of re-rendering down the tree at that point.

In this example, React will only trigger a re-render of ChildComponent if either of the props name or id has changed value:

const ChildComponent = React.memo(({ name, id }) => {
return (
<View>
<Text>{name}</Text>
<Text>{id}</Text>
</View>
);
});

const ParentComponent = () => {
<ChildComponent name="Steve" id={123} />;
};

Because those props are hardcoded primitive values, when React compares them

// Not actual React code, just an example
const propsComparison = (oldProps, newProps) => {
return oldProps.name === newProps.name && oldProps.id === newProps.id;
};

it will evaluate them to be the same, because their value is the same, and it will skip the re-render.

The same applies to dependency arrays in React hooks.

const ChildComponent = ({ name }) => {
const modifiedName = useMemo(() => {
// logic
}, [name]);

return <Text>{modifiedName}</Text>;
};

const ParentComponent = () => {
<ChildComponent name="Steve" />;
};

When React compares the old and new values of the dependency array, it will evaluate name === name as true, because it is a primitive with the same value.

This equality comparison behaves a little less expectedly when it receives non-primitive props:

const ChildComponent = React.memo(({ names }) => {
return (
...
);
});

const ParentComponent = () => {
<ChildComponent names={['Steve', 'Stever', 'Stevest']} />;
};

Because we have passed an array literal to the names prop, every time ParentComponent re-renders it will recreate that array, meaning it will have a new reference. Because React.memo is performing a shallow equality comparison it will evaluate that prop to have changed and trigger a re-render, because as mentioned [] === [] is false.

The topic of equality comparison in React will be covered much more thoroughly in our section on memoisation in React.

Going forwards the concept of equality comparison is important to understand.