Avoid re-rendering a big list of items with react-redux

I am using redux with react and typescript for my application. I am working with many items used at different places of my app. My state looks like this:

{
    items: {42: {}, 53: {}, ... }, //A large dictionary of items
    itemPage1: {
        itemsId: [ 42, 34, 4 ],
        ...
    },
    itemPage2: { ... 
    },
    ...
}

The user can modify some attributes of the items dispatching some actions. When this happen I need to redraw the components that have been modified in each pages. The issue is that my items are quite big and I cant afford to redraw all of them at each small modification. I was wondering is this approach would work:

  • I have a fist component <ItemPage1> which connects to the store to get all of the states stored in the tree under itemPage1 e.g. the list of items id: itemsId.
  • Inside <ItemPage1>, I loop over the itemsId property to generate multiple FilterItem components: itemsId.map( itemId => return <FilterItem id=itemId>);
  • Finally each Item is connected using ownProps to get the correct part of the state:

    const mapStateToItemProps = (state, ownProps) => {
        return {
            item: state.items[ownProps.id],
        }
    }
    const mapDispatchToItemProps = (dispatch, ownProps) => {
        return null;
    }
    const FilterItem = connect(
        mapStateToItemProps,
        mapDispatchToItemProps
    )(Item)
    

Can you confirm or refute that if I update the item of id 42, then only this item is going to be re-rendered ?

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

When rendering big list you need to take into considerations few things :

  • Lower the total number of DOM elements that you need to render (by not rendering items that are not actually visible on the screen, also known as virtualization)
  • Don’t re-render items that have not changed

Basically, what you want to avoid is a complete re-render of your list (or your page) when the user edits one single row. This can be achieved exactly how you did it, i.e : by passing to the list container only the ids of items that need to be rendered, and to map over these ids to connect each component by using ownProps. If you have a dump <Item/> component, your <ItemPage/> component will create connected connect(<Item/>) component.

This is going to work, if your put a console.log('item rendered') in your <Item/> component class you will notice that there is only one call.

BUT (and it’s a big but), what is not obvious when working with react-redux is that all connected components that depends on their ownProps will always rerender if any part of the state change. In your case, even if the <Item/> components will not re-render, their wrapped component connect(Item) will ! If you have few dozens of items, you might encounter some latency if actions need to be dispatched quickly (for example when typing in an input). How to avoid that ? Use a factory function to use ownProps as the initial props :

const mapStateToItemProps = (_, initialProps) => (state) => {
    return {
        item: state.items[initialProps.id],  // we're not relying on the second parameters "ownProps" here, so the wrapper component will not rerender
    }
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
    return null;
}
const FilterItem = connect(
    mapStateToItemProps,
    mapDispatchToItemProps
)(Item)

I suggest you to take a look to this other answer.

You might also be interested in these excellent slides : Big List High Performance React & Redux

And finally, you should definitively take a look to react-virtualized to perform the virtualization of your list (i.e, displaying only the item that the user can actually see).

Solution 2

Ok, I’ve found this discussion: https://github.com/reactjs/redux/issues/1303

At the bottom it is clearly stated (from multiple protagonists):

[…] react-redux takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.

[…] Just wanted to fully understand that what’s going on under the hood here, So if the Redux store gets updated but one specific component state hasn’t changed, Redux won’t trigger the forceUpdate() method for that component? […]

The wrapper component generated by React-Redux’s connect() function does a several checks to try to minimize the number of times your actual component has to re-render. This includes a default implementation of shouldComponentUpdate, and doing shallow equality checks on the props going into your component (including what’s returned from mapStateToProps). So yes, as a general rule a connected component will only re-render when the values it’s extracting from state have changed.

So I believe my implementation is good, it won’t re-render all the items since only one item will see its properties modified.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply