Given that shallowEqual
function is being used in conjunction with React’s shouldComponentUpdate
hook a lot these days, this tip might be of help. shallowEqual
is a function that checks whether two objects are equal based on the first level of key <-> value pairs. That is, the following would result in true
:
const one = { a: 1 };
const two = { a: 1 };
shallowEqual(one, two) // => true
Whereas, the following will result in false
:
const one = { a: { b: 1 } };
const two = { a: { b: 1 } };
shallowEqual(one, two) // => false
If the value of any key is an object or a function (UPDATE1), the response will be false
. A common mistake that you might be doing is use a shouldComponentUpdate
function with shallowEqual
comparison function, but pass a callback as a prop. For example:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps) {
return !shallowEqual(this.props, nextProps);
}
}
// Somewhere else:
<MyComponent onClick={handleOnClick} {...rest} />
// Assume that the value of `deepObject` is an object that's > 1 level
// deep.
const deepObject = { a: { b: 2 } };
<MyComponent {...rest} deepObject={deepObject}/>
Since one of the props in the above code is a function or a complex object that’s getting passed from the parent, the child’s (MyComponent
) shouldComponentUpdate
will always return true
, which in turn triggers a re-render of the component, and all its children. This is particularly problematic when this component has a deeply nested structure. Do watchout for this pattern. Note that this is not exactly a problem with the implementation of the function, but the way it’s used.
In the current codebase I’m working on, there are two utilility functions omit
and pick
that work on objects similar to how the identically named functions in Lodash work. I’ve started using these to filter out props
to include just the ones I need. For example:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps) {
const filteredNextProps = omit(nextProps, ['onClick']);
const filteredCurrentProps = omit(this.prop, ['onClick']);
return !shallowEqual(filteredNextProps, filteredCurrentProps);
}
}
// Somewhere else:
<MyComponent onClick={handleOnClick} {...rest} />
Good thing about Lodash is that both these functions are available as individual modules, so use them at will. It also has a _.isEqual
function that performs a deep-check of objects. This will solve the deep object problem somewhat, but won’t solve it when one of the values in the object is a function.
Update
The case of function is a bit complicated than the one provided in the example above. The naive way of passing a function is doing something like:
class MyComponent extends React.Component {
handleClick = () => { this.setState({ clicked: true }); }
render () {
return (
<a href='javascript:void(0)' onClick={() => this.setState({ clicked: true })>
<ChildComponent func={this.doSomething} />
</a>
);
}
}
class ChildComponent extends React.Component {
shouldComponentUpdate (nextProps) {
console.log(nextProps.func === this.props.func); // false, always
return true;
}
render () {
return <div>Child</div>;
}
}
<MyComponent />
This type of code creates a binding for each render, so the func
prop gets a new function every time the component renders. This is when the simpler form of shallowEqual
fails. A different way to achieve the same effect, but without creating the new binding every time is by defining a property function for the class:
class MyComponent extends React.Component {
doSomething = () => null // actually do something here
handleClick = () => { this.setState({ clicked: true }); }
render () {
return (
<a href='javascript:void(0)' onClick={this.handleClick}>
<ChildComponent func={this.doSomething} />
</a>
);
}
}
class ChildComponent extends React.Component {
shouldComponentUpdate (nextProps) {
console.log(nextProps.func === this.props.func); // true
return true;
}
render () {
return <div>Child</div>;
}
}
<MyComponent />
-
This time, no new instance is getting created for each render. I thought I’d clear this up after reading an excellent post on React performance tips by Saif Hakim. ↩