2

When rendering a long list of elements in a table for example, then any call to setState regardless of whether it changes the data the table uses for enumeration results in a re-render of every child object.

Every check/uncheck will re-render every element. This causes major slowdown with 2000 elements that are more complex. I will use virtualization, but want to make sure this is as performant as possible before doing so.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
    const rows = Array.from({ length: 200 }, (v, k) => k + 1).map(i => ({
      id: i,
      field1: "hello",
      field2: "cruel",
      field3: "world"
    }));

    this.state = {
      selectedIds: [],
      rows
    };
  }

  onChange(e) {
    const name = e.target.name;
    const checked = e.target.checked;
    const selectedIds = [...this.state.selectedIds];
    if (!checked) {
      const index = selectedIds.findIndex(x => x === name);
      selectedIds.splice(index, 1);
    } else {
      selectedIds.push(name);
    }
    this.setState(state => ({ selectedIds }));
  }

  render() {
    const { rows, selectedIds } = this.state;
    return (
      <div className="App">
        <h5>{selectedIds.length} Rows Selected</h5>
        <table>
          <thead>
            <tr>
              <td>Select</td>
            </tr>
            <tr>
              <td>Field 1</td>
            </tr>
            <tr>
              <td>Field 2</td>
            </tr>
            <tr>
              <td>Field 3</td>
            </tr>
          </thead>
          <tbody>
            {rows.map(row => {
              console.log(row);
              return (
                <tr key={row.id}>
                  <td>
                    <input
                      type="checkbox"
                      onChange={this.onChange}
                      name={row.id}
                    />
                  </td>
                  <td>
                    <div>{row.field1}</div>
                  </td>
                  <td>
                    <div>{row.field2}</div>
                  </td>
                  <td>
                    <div>{row.field3}</div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    );
  }
}

I see from other answers that it is the expected behaviour to re-run the render function

ReactJS - Does render get called any time "setState" is called?

But if so, when the number of elements is increased to say 4000 in that link, why is there so much slowdown when clicking? Significant slowdown is noticed when only using 200 items when using slightly more complex custom React Components.

tic
  • 2,484
  • 1
  • 21
  • 33
  • Taking @ShawnAndrews answer one step farther, you should possibly consider [shouldComponentUpdate](https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation) within the `RowItem` component to signal to React that it may not need to be re-rendered based on whatever props. Also `PureComponent` could help. Also, this is what libraries like [recompose](https://github.com/acdlite/recompose) and Redux, depending on structure, can help with as well. – Alexander Staroselsky Nov 19 '18 at 23:02

1 Answers1

1

To solve this issue you'll want to create an additional component for the row item. This way it will have a separate render function and not rerender itself because the row component hasn't changed, only the table component.

{rows.map(row => {
    return (
        <RowItem row={row} onChange={this.onChange}>
    );      
}}

and your new component will look something like this:

class RowItem extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
        <tr key={this.props.row.id}>
          <td>
            <input
              type="checkbox"
              onChange={this.props.onChange}
              name={this.props.row.id}
            />
          </td>
          <td>
            <div>{this.props.row.field1}</div>
          </td>
          <td>
            <div>{this.props.row.field2}</div>
          </td>
          <td>
            <div>{this.props.row.field3}</div>
          </td>
        </tr>
    );
  }  
}
Shawn Andrews
  • 1,432
  • 11
  • 24
  • 2
    I think you'll want RowItem to extend `React.PureComponent` so that it will only try to render if its props change. Without this, it still won't update the DOM unnecessarily (since it will detect that the rendered result is the same), but it will still execute RowItem's render method (and compare to DOM) unnecessarily. – Ryan Cogswell Nov 19 '18 at 23:04
  • I agree with Ryan, simply change React.Component -> React.PureComponent to ensure your against needless re-rendering – Shawn Andrews Nov 21 '18 at 05:27