52

Let's say I have a list of 1000 items. And I rendering it with React, like this:

class Parent extends React.Component {
  render() {
    // this.state.list is a list of 1000 items
    return <List list={this.state.list} />;
  }
}

class List extends React.Component {
  render() {
    // here we're looping through this.props.list and creating 1000 new Items
    var list = this.props.list.map(item => {
      return <Item key={item.key} item={item} />;
    });
    return <div>{list}</div>;
  }
}

class Item extends React.Component {
  shouldComponentUpdate() {
    // here I comparing old state/props with new
  }
  render() {
    // some rendering here...
  }
}

With a relatively long list map() takes about 10-20ms and I can notice a small lag in the interface.

Can I prevent recreation of 1000 React objects every time when I only need to update one?

ch1p_
  • 1,193
  • 2
  • 8
  • 15
  • Did you look into `shouldComponentUpdate()`.If not checkout [here](https://facebook.github.io/react/docs/advanced-performance.html). It might help your needs. – Praveen Raj Oct 12 '15 at 12:32
  • 1
    @PraveenRaj yes, I use it to avoid rerendering items that hasn't been changed. But I wondering how to avoid looping through the list (and creating Items) every time, and shouldComponentUpdate can't help here. – ch1p_ Oct 12 '15 at 12:39
  • Is your list only ever added to and modified, or can items be removed too? – Dominic Oct 12 '15 at 12:48
  • @DominicTobias items can be added, removed or rearranged. – ch1p_ Oct 12 '15 at 12:50
  • ES7 `Array.observe()` may be [useful](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe#Browser_compatibility).But it is supported in chrome alone@ch1p – Praveen Raj Oct 12 '15 at 12:56
  • I don't think you're going to get it more performant unless you maintain something like a keyed list and keep track of what has been modified and intelligently only update what has been changed.. a bit like how the virtual DOM works (basically it would be complicated) – Dominic Oct 12 '15 at 13:01
  • 2
    React uses the keys to do smart updates: if all 1000 keys change between 2 render passes, then this may be cause of the delay. Make sure the only key that is different is the key of the item that actually changed. – wintvelt Oct 12 '15 at 21:16
  • To give you the best answer it would help to shed some more light on whats in your list and what the UI is doing on updates. In this example there is a global list and its children are updated pretty efficiently. See here: http://www.react.run/HykqnmHbl/24 – skav Nov 13 '16 at 00:35
  • 1
    If the update to a particular `Item` is occurring due to a state change in the component itself, then its parents won't be updated. So if possible, try to change its state using some external variables or from within the component itself. –  Nov 14 '16 at 14:58
  • @wintvelt none of the keys should change, the key should be the `item.key` like @ch1p already does. So when an item is updated, it's key should remain the same. – ArneHugo Nov 16 '16 at 13:24
  • @ArneHugo you are correct that if an existing item changes inside, or if item does not change, the key should remain the same. If one item is replaced by another item, the new item should have a new key. But the question only states that there are 1000 items, each with its own key. The question does not state if all or none or some of the keys change between render passes. – wintvelt Nov 16 '16 at 14:15
  • @wintvelt I think I understand what you mean. I assumed the key for each item would be static for that item, and that only one or a few items would be added, removed or changed at a time. – ArneHugo Nov 16 '16 at 14:18
  • By the way, [this answer](http://stackoverflow.com/a/35993596/2054731) to a similar question may be of interest. – ArneHugo Nov 17 '16 at 11:27

6 Answers6

23

You can do it by using any state management library, so that your Parent doesn't keep track of this.state.list => your List only re-renders when new Item is added. And the individual Item will re-render when they are updated.

Lets say you use redux.

Your code will become something like this:

// Parent.js
class Parent extends React.Component {
  render() {        
    return <List />;
  }
}
// List.js
class List extends React.Component {
  render() {        
    var list = this.props.list.map(item => {
      return <Item key={item.key} uniqueKey={item.key} />;
    });
    return <div>{list}</div>;
  }
}

const mapStateToProps = (state) => ({
  list: getList(state)
});

export default connect(mapStateToProps)(List);
 
// Item.js
class Item extends React.Component {
  shouldComponentUpdate() {
  }
  render() {
  }
}

const mapStateToProps = (state, ownProps) => ({
  item: getItemByKey(ownProps.uniqueKey)
});

export default connect(mapStateToProps)(Item);

Of course, you have to implement the reducer and the two selectors getList and getItemByKey.

With this, you List re-render will be trigger if new elements added, or if you change item.key (which you shouldn't)

Ouroborus
  • 16,237
  • 4
  • 39
  • 62
xiaofan2406
  • 3,250
  • 5
  • 26
  • 34
  • 3
    This is the way to do it. Perhaps begin your answer with how the data does not have to travel through the `Parent` and `List` components, so that they don't need to re-render (and therefore all their children don't need to re-render). – ArneHugo Nov 16 '16 at 12:59
  • In my case `connect`ing `Item` made it two times slower to check `shouldComponentUpdate` for 1000 elements. (from 250-300 ms. it rose up to 500-600). If possible, let the parent pass the props to the children. – Al.G. Apr 08 '18 at 18:40
  • @Al.G. I am very interested to know your use case, is there a repo I can check? – xiaofan2406 Apr 09 '18 at 08:41
  • Sure... I'll make a mcve later today and will share a link. – Al.G. Apr 09 '18 at 08:48
  • @xiaofan2406 now reading over your post again, I see you say say *List should only update when items are deleted/removed*, though you don't override `shouldComponentUpdate` in your post. When I tested my code, I had explicitly told List to rerender anytime `this.props.list` changes. I think on single item update this caused every item to be checked for `shouldComponentUpdate` once in `List.render()`, and once by redux. When I fixed `List.shouldComponentUpdate()` to check `return this.props.list !== newProps.list && this.props.list.length !== newProps.list.length`, it run a lot faster. – Al.G. Apr 11 '18 at 15:43
  • @Al.G. thats dependent on how you `connect`. If the `this.props.list` array for `List` gets a new reference every time then adding `shouldComponentUpdate` makes sense. However normally, I would avoid using `shouldComponentUpdate` completely, and investigate how to structure data differently, so that the array reference only changes when a new item is added. `reselect` package can offer a lot of help in this case, but it is still highly dependent on the data structure. – xiaofan2406 Apr 12 '18 at 03:03
  • @xiaofan2406 redux requires reducers to be pure functions so when I need to update the `list` array in the state, I need to clone it. Therefore, when an item needs to be change, the whole list is copied. This causes the `List` component to need redraw. But redraw isn't needed (except when elements are added/removed) so I have to implement `shouldComponentUpdate` to check if `list` changed its size and redraw only at that time. Do you suggest that I modify the state directly on single item update? It won't trigger `List` redraw so it'd be more efficient. Not sure if single Items will redraw. – Al.G. Apr 12 '18 at 08:10
  • @Al.G. if you use redux with reselect then lookups are cached – Meredith Jun 24 '19 at 03:17
11

EDIT:

My inital suggestions only addressed possible efficiency improvements to rendered lists and did not address the question about limiting the re-rendering of components as a result of the list changing.

See @xiaofan2406's answer for a clean solution to the original question.


Libraries that help make rendering long lists more efficient and easy:

React Infinite

React-Virtualized

Community
  • 1
  • 1
Pineda
  • 7,435
  • 3
  • 30
  • 45
  • 1
    of all the infinite loaders react-virtualized is one that should always be considered. In any case, partial rendering may or may not be the right answer here. They may need data virtualization. – skav Nov 13 '16 at 00:37
  • 3
    This limits the number of React components that are reandered, but _does not_ prevent re-rendering unchanged components (see answer by xiaofan2406). I believe the latter is a better solution to to speed up updates, while your solution should be used to speed up initial render. The two solutions can of course be used together. – ArneHugo Nov 16 '16 at 13:13
  • 1
    @ArneHugo, fair point. I've amended my answer to avoid it become a distraction to the original question. – Pineda Nov 16 '16 at 14:24
7

When you change your data, react default operation is to render all children components, and creat virtual dom to judge which component is need to be rerender.

So, if we can let react know there is only one component need to be rerender. It can save times.

You can use shouldComponentsUpdate in your list component.

If in this function return false, react will not create vitual dom to judge.

I assume your data like this [{name: 'name_1'}, {name: 'name_2'}]

class Item extends React.Component {
  // you need judge if props and state have been changed, if not
  // execute return false;
  shouldComponentUpdate(nextProps, nextState) { 
    if (nextProps.name === this.props.name) return false;

    return true;
  }
  render() {
    return (
      <li>{this.props.name}</li>
   )
  }
}

As react just render what have been changed component. So if you just change one item's data, others will not do render.

qiuyuntao
  • 2,314
  • 4
  • 19
  • 24
  • You can also use [React.PureComponent](https://facebook.github.io/react/docs/react-api.html#react.purecomponent) to do basically the same. It does a [shallow compare](https://facebook.github.io/react/docs/shallow-compare.html) of props in `shouldComponentUpdate`. Also, when it's possible to only update the state or props of the lowermost components that will change, that has even better performance (see answer of xiaofan2406). Both techniques can be used together. – ArneHugo Nov 16 '16 at 15:44
  • 1
    I think you didn't understand the question, is not about passing again the array as `prop` and detect if should update or not, is about, how updated only a specific item of that list, not executing render() method again. – ncubica Feb 18 '17 at 03:11
1

There are a few things you can do:

  1. When you build, make sure you are setting NODE_ENV to production. e.g. NODE_ENV=production npm run build or similar. ReactJS performs a lot of safety checks when NODE_ENV is not set to production, such as PropType checks. Switching these off should give you a >2x performance improvement for React rendering, and is vital for your production build (though leave it off during development - those safety checks help prevent bugs!). You may find this is good enough for the number of items you need to support.
  2. If the elements are in a scrollable panel, and you can only see a few of them, you can set things up only to render the visible subset of elements. This is easiest when the items have fixed height. The basic approach is to add firstRendered/lastRendered props to your List state (that's first inclusive and last exclusive of course). In List.render, render a filler blank div (or tr if applicable) of the correct height (firstRendered * itemHeight), then your rendered range of items [firstRendered, lastRendered), then another filler div with the remaining height ((totalItems - lastRendered) * itemHeight). Make sure you give your fillers and items fixed keys. You then just need to handle onScroll on the scrollable div, and work out what the correct range to render is (generally you want to render a decent overlap off the top and bottom, also you want to only trigger a setState to change the range when you get near to the edge of it). A crazier alternative is to render and implement your own scrollbar (which is what Facebook's own FixedDataTable does I think - https://facebook.github.io/fixed-data-table/). There are lots of examples of this general approach here https://react.rocks/tag/InfiniteScroll
  3. Use a sideways loading approach using a state management library. For larger apps this is essential anyway. Rather than passing the Items' state down from the top, have each Item retrieve its own state, either from 'global' state (as in classical Flux), or via React context (as in modern Flux implementations, MobX, etc.). That way, when an item changes, only that item needs to re-render.
TomW
  • 3,923
  • 1
  • 23
  • 26
0

One way to avoid looping through the component list every render would be to do it outside of render function and save it to a variable.

class Item extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return this.props.item != nextProps.item;
    }

    render() {
        return <li>{this.props.item}</li>;
    }
}

class List extends React.Component {
    constructor(props) {
        super(props);
        this.items = [];
        this.update = this.update.bind(this);
    }

    componentWillMount() {
        this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
    }

    update(index) {

        this.items[index] = <Item key={index} item={'item changed'} />
        this.forceUpdate();
    }

    render() {
        return <div>
            <button onClick={() => { this.update(199); }}>Update</button>
            <ul>{this.items}</ul>
        </div>
    }
}
jpdelatorre
  • 3,573
  • 1
  • 20
  • 25
  • Using `forceUpdate` is an antipattern and should be avoided where possible. In this case it's possible (and therefore better) to receive changes in the lowermost component that "renders the data", as suggested by xiaofan2406. – ArneHugo Nov 16 '16 at 13:17
  • I'm aware about using `forceUpdate` as anti-pattern but the OP was asking if he can avoid looping through the items. I don't think there will be performance gain from xiaofan2406's solution and will just add overhead. Using `shouldComponentUpdate` on the item would have the same effect or even faster. – jpdelatorre Nov 17 '16 at 00:33
  • xiaofan2406's solution will definitely improve performance, try it if you are in doubt. `shouldComponentUpdate` will not have as good performance, because it would be called for each and every item for each change. xiaofan2406's solution does not trigger any calls to any components other than the items that are changed. I don't think you should worry about the computation overhead for running redux and react-redux, the overhead is very small and it gives more robust and understandable code. – ArneHugo Nov 17 '16 at 11:23
0

There is a way to do this, but I don't think the React team would call it Kosher so to speak. Basically, instead of the parent having state, it has lists of refs. Your components have their own state. Your components are created once in the parent and stored in a parent's ref property which holds an array of all those components. So the components are never recreated on each rerender, and instead are persisted. You also would need a list of refs that attach to a function in each component to allow the parent to call individual components (in hooks you can use imperativehandle to do this).

Now, when the user does something that would cause the data to change for a specific component in that list, you would find that component's ref in the list of attached functions and call the function on it. The component could then update and rerender itself based off that function call from its parent, without other components being affected/recreated/rerendered.

I believe this is called imperative programming rather than declarative, and React doesn't like it. I personally have used this technique in my own projects for similar reasons to you, and it worked for me.

Jordy
  • 415
  • 5
  • 13