Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: update one item in a list without recreating all items

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?

like image 669
ch1p_ Avatar asked Oct 12 '15 12:10

ch1p_


People also ask

How do you update an item in a list React?

In ReactJS, changing items in the list when an item of the list is clicked can be done by triggering the event onClick() on the item which is currently clicked. For, reflecting the change, we also have to maintain the state in react so that after change when the page render again the changes get reflected.

How do I create a dynamic list in React?

To render a list dynamically in React, we start by adding a property to the state object. We can populate that property with an array of strings like so. Great. Now we want to move to the render() method to render each list item dynamically.

Why do we use shouldComponentUpdate () function in ReactJS?

Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.

What is Rerendering in React?

A second or subsequent render to update the state is called as re-rendering. React components automatically re-render whenever there is a change in their state or props.


4 Answers

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)

like image 141
xiaofan2406 Avatar answered Oct 28 '22 14:10

xiaofan2406


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

like image 11
Pineda Avatar answered Oct 28 '22 14:10

Pineda


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.

like image 7
qiuyuntao Avatar answered Oct 28 '22 16:10

qiuyuntao


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.
like image 1
TomW Avatar answered Oct 28 '22 15:10

TomW