ReactJS : Simple performance tip for list or table

28 May 2018

If you implement table or list of your own, this simple tip will gain some performance improvement and can save a lot of rerenders.

Consider we have a Table and TableRow component which we use to show large Tabular data which has checkboxes used by users to select the rows.

// data source
const users = [
  {id: 1, name: 'A'},
  {id: 2, name: 'B'},
  {id: 3, name: 'C'},
  {id: 4, name: 'D'},
];

class Table extends React.Component {
  constructor() {
    super();

    this.state = {
      selected: {},
    }
  }

  handleSelect = (e) => {
    const selected = this.state.selected;
    selected[e.target.name] = e.target.checked;
    this.setState({ selected });
  }

  render() {
    return (
      <table>
        <thead>
          <tr>
            <th />
            <th>ID</th>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {
            users.map((user) => {
              return (
                  <TableRow 
                    key={user.id} 
                    id={user.id} 
                    name={user.name}  
                    selected={this.state.selected[user.id]} 
                    handleSelect={this.handleSelect} 
                  />;
              );
            })
          }
        </tbody>
      </table>
    );
  }
}

And the TableRow component

const TableRow = ({ selected, id, name, handleSelect }) => {
  console.log(`render TableRow :: ${id} :: ${name}`);
  return (
    <tr>
      <td>
        <input 
          name={id} 
          type="checkbox" 
          checked={selected} 
          onChange={handleSelect} 
        />
      </td>
      <td>{id}</td>
      <td>{name}</td>
    </tr>
  );
}

TableRow.defaultProps = {
  selected: false
}

The TableRow component have a console.log which will log the id & name when ever the component renders. This will help us to know which all rows are getting rerendered.

Now when the user select one of row, all the rows get rerendered in the above example. You can see console.log for all the items in the data source.

react-before-optimised-table

You can see the live version in jsbin

JS Bin on jsbin.com

In the above demo we have only 4 items and 3 columns, which didn’t cause much performance degradation. Where as consider the above table with around 200 items and 50 columns? A simple checkbox selection will trigger 200 rerenders for TableRow component.

Using PureComponent

The simple and effective optimisation we can do here is convert the TableRow component to a PureComponent.

class TableRow extends React.PureComponent {
  defaultProps = {
    selected: false
  }

  render() {
    const { selected, id, name, handleSelect } = this.props;
    console.log(`render TableRow :: ${id} :: ${name}`);
    return (
      <tr>
        <td>
          <input 
            name={id} 
            type="checkbox" 
            checked={selected} 
            onChange={handleSelect} 
          />
        </td>
        <td>{id}</td>
        <td>{name}</td>
      </tr>
    );
  }
}

Now lets try selecting one of the row and see the improvement.

react-before-optimised-table

Now when we select a row, only that row rerenders. PureComponent has implemented shouldComponentUpdate which does a shallow compare of props and do rerenders only if it differs.

You can see the live version in jsbin

JS Bin on jsbin.com

In the demo, the using of PureComponent was possible because the props where number & string. If the props are Array or Object we won’t be able to use PureComponent since the shallow compare of PureComponent might lead to false positives.

In such cases we can implement write our own parent component which implements deep compare in shouldComponentUpdate.

class PerfComponent extends Component {
  shouldComponentUpdate(nextProps) {
    // implement/ use deep compare functionality
    if(!deepEqual) {
      return true;
    }
    return false;
  }
}

export default PerfComponent;

and inherit TableRow from PerfComponent.

class TableRow extends PerfComponent {
  defaultProps = {
    selected: false
  }

  render() {
    const { selected, user, handleSelect } = this.props;
    console.log(`render TableRow :: ${user.id} :: ${user.name}`);
    return (
      <tr>
        <td>
          <input 
            name={user.id} 
            type="checkbox" 
            checked={selected} 
            onChange={handleSelect} 
          />
        </td>
        <td>{user.id}</td>
        <td>{user.name}</td>
      </tr>
    );
  }
}

Hope it helped.

Versions of Language/packages used in this post.

| Library/Language | Version |
| ---------------- |---------|
|      React       |  16.4.0 |
If you find my work helpful, You can buy me a coffee.