Reference Identity in Javascript — React/Performance

Valter Júnior
3 min readNov 20, 2018

--

I was reading React's Context API docs these days and came across upon a term that caught my attention.

Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. (https://reactjs.org/docs/context.html#caveats)

reference identity

that's the term.

I believe you are familiar with this term or at least with the identity part. Why am I saying this? Because there's a javascript feature intimately related with the concept we're talking about. I'm pretty sure you've already saw or used this:

// Identity / strict equality (===)x === y

As most of Javascript developers know the identity operator returns true if the operands are strictly equal with no type conversion.

What's identity, anyway?

An identity describes the property of objects that distinguishes them from other objects. Simple as that.

What’s reference identity, then?

As discussed before, the identity describes the property of objects that distinguishes them from other objects. So, the reference identity is nothing more than the use of the object reference as identity.

Variables that are assigned a non-primitive value are given a reference to that value. That reference points to the object’s location in memory. The variables don’t actually contain the value.

Going back to our example,

// Identity / strict equality (===)x === y 

the comparisonx === y will return true only if the reference of the objects are the same. If you think a little bit about the name of this === operator, you’ll understand why it behaves like it does. It’s comparing the objects identity, which is the reference.

Why this matters in React in terms of performance?

As discussed before, React context uses reference identity to determine when to re-render. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:

class App extends React.Component {
render() {
return (
<Provider value={{key: 'value'}}>
<Toolbar />
</Provider>
);
}
}

This is happening because every time you do something like = {} you’re creating a new object. So, the above approaches are basically the same:

var obj1 = new Object();  
var obj2 = {};

Let’s see another example:

class Table extends PureComponent {
render() {
return (
<div>
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || []} />
)}
</div>
);
}
}

You see the options array was passed deep down in the Cell elements. Normally this would not be an issue. The other Cell elements would not be re-rendered because they can do the cheap shallow equality check and skip the render entirely but in this case when the options prop is null then the default arrays is going to be used. As you should know the array literal is the same as new Array()which creates a new array instance. This completely destroyed every pure render optimization inside the Cell elements. In Javascript different instances have different identities and thus the shallow equality check always produces false and tells React to re-render the components.

The fix was easy:

const default = [];
class Table extends PureComponent {
render() {
return (
<div>
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || default} />
)}
</div>
);
}
}

I spent some time to realize that and this simple post aims to help other people in the same situation. Hope it helped!

--

--

Valter Júnior

Javascript Developer from Brazil. Entrepreneur. Husband. #react #redux #react-native #es6/* #node #graphql