Skip to content

Instantly share code, notes, and snippets.

@theKashey
Last active April 7, 2019 00:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theKashey/9ce746aca9b623520d46a7cf49fe1261 to your computer and use it in GitHub Desktop.
Save theKashey/9ce746aca9b623520d46a7cf49fe1261 to your computer and use it in GitHub Desktop.

#React optimizations

shouldComponentUpdate

The main reason why our UI felt sluggish was because we were rendering components unnecessarily. When one component in the UI was interacted with, many unrelated components would be re-rendered because application state was “touched” and many derived props were re-computed.

An easy way to improve this situation is to use React’s built-in PureComponent instead of plain Component. PureComponent implements the the lifecycle method shouldComponentUpdate, in which it performs a shallow comparison of props and state to decide if a re-render is necessary.

However, in many cases in our application properties in props are derived from the Redux state. This means that the properties might be different by reference, even though they have the same value. Luckily we can remedy this by doing a deep comparison in shouldComponentUpdate.

shouldComponentUpdate(nextProps: Props, nextState: State) {
  return !R.equals(this.props, nextProps) || !R.equals(this.state, nextState);
}

Here we are using Ramda’s equals to compare state and props.

shouldComponentUpdate is also useful when you want to have special handling for some properties. For example, for some of our components it doesn’t make much sense to re-render during scrolling even if other props have changed:

shouldComponentUpdate(nextProps: Props, nextState: State) {
  return !nextProps.isScrolling &&
    (!R.equals(this.props, nextProps) || !R.equals(this.state, nextState));
}

Disclaimer: It’s important to note that this isn’t a one-size-fits-all solution. Deeply comparing objects can be expensive and if the property actually changed then you just end up adding additional computation time while achieving nothing.

Redux

Batch actions

In many cases in our application we have callbacks which dispatch multiple actions. It’s often not necessary for the effects of each action to happen individually, and even harmful when this results in the UI showing in an inconsistent state. We can save a lot of effort by suspending store notifications until all actions are dispatched. If you’re interested, see here for how we enhance the Redux store to handle suspending notifications.

connect options

Redux’s connect() function supports it’s own version of shouldComponentUpdate. Namely, it has functions areStatesEqual, areOwnPropsEqual, areStatePropsEqual and areMergedPropsEqual which can be passed in as options. By providing these functions (and making them perform a deep-ish comparison) we can stop unnecessary re-renders before the Component level.

export default connect(mapStateToProps, mapDispatchToProps, undefined, {
    areOwnPropsEqual: R.equals,
    areStatePropsEqual: R.equals,
})(View);

Reselect

It’s a good idea to use a library like reselect with Redux to reduce computational load when deriving data from the app state. There are few tricks to make it even better.

createSelector with custom equalityCheck

createSelector, by default, compares by value. This gives a lot of false positives in any real-world derived data graph. You can define your own createSelector using createSelectorCreator and passing custom equalityCheck function to it but, as we’ve seen with shouldComponentUpdate, doing full-depth comparison by value is expensive. We use a 2 level deep comparison with simplified object handling (not sorting keys).

Write your selectors carefully

Often times performance degradations happen when selectors are inefficient. There are a few problems that we ran into that were dramatically slowing down our application

Selectors that aren’t specific enough. Selectors should not depend on data that they do not use.

Using a uniq function which used a deep comparison to determine unique values in an array. In most cases we don’t need comparison by value, and Array.from(new Set(…)) is enough.

Returning items from selectors that are opaque to comparison (i.e. functions).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment