Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add new elements without re-rendering whole list with React

I'm working on simple stream app. I have list of posts and this list can receive updates, which will display on top of it.

The problem is on each new post receive React rerenders the whole list of elements. I've made simple example for it.

Is there any way to avoid this behaviour? I've seen the dynamic-children topic on React docs, but in example, as you see, I have all children updated anyway.

class Post extends React.Component {
  render() {
    console.log('rerendered post', this.props.reactKey);
    return (
      <li>{this.props.post.text}</li>
    );
  }
}

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {posts: [
      {id: '00001', text: 'First one'},
      {id: '00002',text: 'Second one'},
      {id: '00003',text: 'Third one'}
    ]};
  }

  addPost() {
    const posts = this.state.posts;
    posts.unshift({id: '00004', text: 'New post'});
    this.setState({posts: posts});
  }

  render() {
    return (
      <div>
        <button onClick={this.addPost.bind(this)}>Add Post</button>
        <ul>
          {this.state.posts.map((post, index) => {
            return (<Post post={post} key={post.id} reactKey={index} />);
          })}
        </ul>
      </div>
    );
  }
}


ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<body>
  <div id="root"></div>
</body>

The solution

The problem was that I used index of .map function is a key for each list component instead of unique key. And because after adding new element to list all indexes changes to +1, so the first post becomes the second, all my posts have re-rendered. So at first, check you use unique keys across all list elements :-)

like image 951
Nikolay Aleshkovskiy Avatar asked Aug 22 '16 14:08

Nikolay Aleshkovskiy


People also ask

How do you avoid multiple re renders in React?

1. Memoization using useMemo() and UseCallback() Hooks. Memoization enables your code to re-render components only if there's a change in the props. With this technique, developers can avoid unnecessary renderings and reduce the computational load in applications.

How do you update list items in 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.

Does changing props cause re-render?

In order for props to change, they need to be updated by the parent component. This means the parent would have to re-render, which will trigger re-render of the child component regardless of its props.

How do I stop infinite rendering in React JS?

To get rid of your infinite loop, simply use an empty dependency array like so: const [count, setCount] = useState(0); //only update the value of 'count' when component is first mounted useEffect(() => { setCount((count) => count + 1); }, []); This will tell React to run useEffect on the first render.


1 Answers

Work that needs to be done only once should be done in a lifecycle method that is guaranteed to run only once, like componentDidMount. As the docs suggest:

If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.

I added logging to componentDidMount in your snippet to show rendering happens many times, but componentDidMount is called only once per instance.

class Post extends React.Component {
  componentDidMount() {
    console.log('mounted post', this.props.id);
  }

  render() {
    console.log('rerendered post', this.props.id);
    return (
      <li>{this.props.post.text}</li>
    );
  }
}

class App extends React.Component {

  constructor(props) {
    super(props);
    this.nextId = 4;
    this.state = {
      posts: [
        {id: 1, text: 'First one'},
        {id: 2,text: 'Second one'},
        {id: 3,text: 'Third one'},
      ],
    };
  }

  addPost() {
    const posts = this.state.posts;
    posts.unshift({id: this.nextId, text: 'Post ' + this.nextId});
    this.nextId++;
    this.setState({posts: posts});
  }

  render() {
    return (
      <div>
        <button onClick={this.addPost.bind(this)}>Add Post</button>
        <ul>
          {this.state.posts.map((post, index) => {
            return (<Post post={post} key={post.id} id={post.id} />);
          })}
        </ul>
      </div>
    );
  }
}


ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<body>
  <div id="root"></div>
</body>
like image 150
Ross Allen Avatar answered Sep 20 '22 04:09

Ross Allen