I often hear people saying Redux is slow and can be a root cause of unnecessary re-rendering. In this article, I’ll try to explain how to avoid common mistakes when working with react-redux binding.

Assuming you already know about basic techniques to avoid reconciliation, otherwise, you can read about it in an official react guide.

There’re lots of common techniques, like using PureComponent, reselect and memoization, however when it comes to a complex application, even following all the rules still opens the gap for performance problems.

Defining root cause

Using react perf tools it’s quite easy to see what part of the application gets re-rendered. But the main question is why it gets re-rendered. To quickly identify that you can use the following snippet:

import { PureComponent } from 'react'; PureComponent.componentDidUpdate = prevProps => {

const name =

this.constructor.displayName || this.constructor.name || 'Component';

console.group(name);

Object.keys(prevProps).forEach(key => {

if (prevProps[key] !== this.props[key]) {

console.log(

`property ${key} changed from ${prevProps[key]} to ${

this.props[key]

}`

);

}

});

console.groupEnd(name);

};

It will pretty print properties, that caused your component to re-render. Try to put this snippet into the root module of your application and then dispatch redux action, that does not exist in your system. I bet you’ll find lots of interesting things.

Common pitfalls

I’ll try to list the most common mistakes breaking equality checks and force your components to always re-render.

Selectors without memoization.

The most obvious one. Selectors like

const getList = (state, filter) => state.list.filter(filter); const getConfig = (state) => {

return {foo: state.foo}

};

will always cause re-render. Try to use reselect or another memoization technique to fix it.

Edge cases handling

Selectors that return a default value if it does not exists also can cause performance regression:

const getList = (state) => state.list || [];

On every redux store changes, it will return a new instance of an array, but you can easily fix it by defining default constant:

const defaultList = []; const getList = (state) => state.list || defaultList;

Logic in connected components

import {connect} from 'react-redux'; ... export default connect((state) => ({

filter: {foo: state.filter}

}))(MyComponent);

Such logic should be moved into a selector and properly memoized.

React elements in props

import {connect} from 'react-redux'; ... export default connect((state) => ({

element: <List />

}));

element will always be a new instance, so it also breaks equality check. But it’s pretty easy to fix by moving it to a constant, defined in the module:

import {connect} from 'react-redux'; ... const element = <List />; export default connect((state) => ({

element

}));

Another way is to pass component instead of instance and delegate rendering to a parent:

import {connect} from 'react-redux'; ... export default connect((state) => ({

Element: List

})); ... {

render() {

const {Element} = this.props; return (<div>{<Element />}</div>);

}

}

Dynamic handlers

class MyComponent extends PureComponent {

render() {

const {title, onClick} = this.props;



return (

<InnerComponent title={title} onClick={() => onClick()} />

)

}

}

InnerComponent will always be re-rendered because it always get’s new instance of onClick handler. You can simply fix it by moving handler to a component class property:

class MyComponent extends PureComponent {

onClick = () => {

this.props.onClick();

}



render() {

const {title, onClick} = this.props;



return (

<InnerComponent onClick={this.onClick}>

<span>{title}</span>

</InnerComponent>

)

}

}

Tips and tricks

Now let’s elaborate on how you can make it easier to avoid such mistakes in the future.

Make components smaller

It’s a quite bad idea of subscribing a container component to a bunch of states and then passing it down via props, for example:

class MyBeastComponent extends PureComponent {

render() {

const {a, b, c} = this.props;



return (

<div>

<ComponentA a={a} />

<ComponentB b={b} />

<ComponentC c={c} />

</div>

);

}

}

Every time something changes in the a, b or c it will cause all the chunk to re-render. Instead, you can split it into smaller connected components:

class MyBeastComponent extends PureComponent {

render() {

return (

<div>

<ComponentA />

<ComponentB />

<ComponentC />

</div>

);

}

} ... ComponentA, ComponentB or ComponentC import {connect} from 'react-redux'; export default connect(state => ({

a: getA(state)

}))(ComponentA);

Reduce data scope

Pass only data that your component needs. For example, to render collection, usually, instead of passing the whole list you can just pass ids of items. Then connect children to the store and use the ID to retrieve the data:

class MyList extends PureComponent {

render() {

const {itemIds} = this.props;



return (

<div>

{itemIds.map(id => (

<Item key={id} id={id} />

))}

</div>

);

}

} ... Item component import {connect} from 'react-redux'; export default connect((state, {id}) => ({

title: getItemTitle(state, id),

foo: getItemFoo(state, id),

bar: getItemBar(state, id)

}))(Item);

In this case, even if set of ids in your collection changes, it won’t re-render all the list including child items.

Double-check yourself

Always make sure your memoization works and you not producing any regressions. You can use the following snippet to wrap you selectors in unit tests and double-check it returns the same result for the same set of arguments:

const wrapMemoizedSelector = (selector) => {

returns (...args) => {

const result = selector(...args); if (selector(...args) !== result) {

throw new Error('Memoization check failed.');

} return result;

}

} ... const examine = wrapMemoizedSelector(selector); test('check return value', () => {

expect(examine({...})).toEqual({...});

});

That’s it!

I hope that will help make you React apps rapid fast.

Clap if it was helpful and share your tips in comments.