3

I have the following code. When changing the value of one element, the entire list is re-render. How can it be avoided by redrawing only one element? I use setState and class components.

import React from "react";
import "./styles.css";

class ListItem extends React.Component {
  
  handleUpdate = () => {
    this.props.onUpdate(this.props.index);
  };

  totalRenders = 0;

  render() {
    
    const { data: { id, value } } = this.props;

    this.totalRenders += 1;
    console.log("Row rendered");

    return (
      <div>
        <strong>id: </strong>{id}: &nbsp;
        <strong>value: </strong>{value} &nbsp;
        <strong>renders count: </strong>{this.totalRenders} &nbsp;
        <button className="button" onClick={this.handleUpdate}> &nbsp;
          Change row value
        </button>
      </div>
    );
  }
}

export default class App extends React.Component {
  state = { list: [{id: 'id 1', value: '11'}, {id: 'id 2', value: '22'}]};

  handleUpdate = (index) => {
    let newState = this.state.list.slice()
    newState[index].value = Math.round(Math.random() + 10);

    this.setState(() => ({
      list: newState
    }));
  };

  render() {
    console.log("App rendered");

    return (
      <div>
        {this.state.list.map((el, index) => (
          <ListItem
            key={el.id}
            data={el}
            index={index}
            onUpdate={this.handleUpdate}
          />
        ))}
      </div>
    );
  }
}

Sandbox: https://codesandbox.io/s/autumn-architecture-ubgh51?file=/src/App.js

Schekhovtsov
  • 101
  • 2
  • 10

3 Answers3

2

If you update your app state then that component is supposed to be updated. That the expected behaviour and that's how it should be behave. Now coming to your question. If changes in a row should not make your entire app re-render. Then you should manage your state in your item and any changes in that should be maintained in that to unnecessary re-render.

Here's an example of how to achieve it. You can replace your ListItem with this and check it yourself.

function UpdatedListItem({ data }) {
  const [row, setRow] = React.useState(data);

  React.useEffect(() => setRow(data), [data]);

  const { id, value } = row;
  console.log("Changes in: ", data);

  function handleChange() {
    setRow({
      ...row,
      value: Math.round(100 + Math.random() * 5)
    });
  }

  return (
    <div>
      <strong>id: </strong>
      {id}: &nbsp;
      <strong>value: </strong>
      {value} &nbsp;
      <strong>renders count: </strong>
      {this.renders} &nbsp;
      <button className="button" onClick={handleChange}>
        &nbsp; Change row value
      </button>
    </div>
  );
}

Singh3y
  • 336
  • 1
  • 7
  • Although you've updated it at the Row level, the list that's in the App state will not be in sync with the changed row, which can introduce issues if the Row component can be remounted for whatever reason – Liam Trampota Sep 14 '22 at 15:19
1

In your case, you can use the shouldComponentUpdate, reference: React document.

Simple sample:

  shouldComponentUpdate(nextProps, nextState) {
    // Custom update conditions.
    return nextProps.data.id !== this.props.data.id;
  }

Full sample:

Edit TextField selectionStart

※Additional Notes

If using function component, you can use memo to do it, reference: React document.

Jay Lu
  • 1,515
  • 8
  • 18
1

One way you can fix this is by using a implementing shouldcomponentupdate but that method should be implemented when you need some performance improvement not just to stop re renderings

This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.

Next thing you can consider is using PureComponent

to implemented in that way you need to change the way you handle state update

handleMyChange = (index) => {

// Don't mutate the state in react it could lead in to unexpected results
// let newState = [...this.state.list];
// newState[index].value = Math.round(Math.random() + 10);

  const newState = this.state.list.map((item, idx) => {
    if (idx === index) {
     // Replacing with a new item, so the other component can understand it's a new item and re-render it.
      return {
        ...item,
        value: Math.round(100 + Math.random() * 5)
      };
    }

    // Return the same item, so the other component do not render since it's the same item.
    return item;
  });

  this.setState(() => ({
    list: newState
  }));
};

class ListItem extends React.PureComponent {
......

React.PureComponent implements it with a shallow prop and state comparison, inorder to detect it as a new update we have to tell the ListItem component the props are different, to do that we should avoid mutating object in the array, instead we have to create a new item and replace the old array element.

Here is an good example of mutating state in react

Here is the updated sandbox

Kalhan.Toress
  • 21,683
  • 8
  • 68
  • 92