How can I prevent the entire list from re-rendering when the state of one component changes?


jameson

I have a list component that takes data and maps the results on a card-like child component to create a list of "cards". Here is an example:

class Card extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeTab: 1
    };
  }

  setActiveTab = (activeTab) => {
    this.setState({ activeTab });
  }

  render() {
    const activeTab = this.state.activeTab;

    return (
      <div className="card">
        <ul className="nav nav-tabs card-header-tabs">
          <li 
            className="nav-item" 
            key={ 1 }
            onClick={ () => this.setActiveTab(1) }
          >
            <a className="nav-link" href="#">Overview</a>
          </li>
          <li 
            className="nav-item" 
            key={ 2 }
            onClick={ () => this.setActiveTab(2) }
          >
            <a className="nav-link" href="#">Data</a>
          </li>
          <li 
            className="nav-item" 
            key={ 3 }
            onClick={ () => this.setActiveTab(3) }
          >
            <a className="nav-link" href="#">Edit</a>
          </li>
        </ul>
        <h3 className="card-title">{ this.props.title }</h3>
        { activeTab === 1 && 
          <h5 className="card-text">
            { this.props.body }
          </h5>    
        }
        { activeTab === 2 && 
          <div className="card-data">
            <a>Yes: { this.props.yes }</a>
            <a>No: { this.props.no }</a>
          </div>
        }
        { activeTab === 3 &&
          <div>
            <a 
              href="/cards"
              onClick={ this.props.onDelete }
              className="delete-card"
            >
              Delete
            </a>
          </div>
        }
      </div>
    )
  }
}
export default Card;


class CardList extends Component {
  componentDidMount() {
    this.props.fetchCards();
  }

  renderCards() {
    return this.props.cards.reverse().map(({ _id, title, body, yes, no }) => {
      return (
        <Card
          key={_id}
          title={title}
          body={body}
          yes={yes}
          no={no}
          onDelete={() => this.props.deleteSurvey(_id)}
        />
      );
    });
  }

  render() {
    return (
      <div className="container">
        <div className="row">{this.renderCards()}</div>
      </div>
    );
  }
}

Each card has tabs, and clicking a tab determines the text displayed in each specific card. The tab/toggle works fine, but my problem is that the whole list is rerendering when the tab is clicked. So if you are at the bottom of the list, re-rendering will bring you back to the top of the list.

I have tried implementing componentShouldUpdate but so far without success. I want the card to switch its content and the list to stay in place. Is it correct to use the shouldUpdate lifecycle method? Or is there a better way?

Kenneth Thrun

In the <a> anchor tag you have href="#" ` which will always redirect you to a new link. (# will redirect to the current page, re-rendering everything).

The easiest solution is to remove the href="#" , this will remove your cursor: pointer;style, but you can add it back into your nav-itemclass name.

Another simple solution is that you can move the onClick into the anchor tag and add evt.preventDefault() (this will prevent the event handler from doing the default action of loading the page to the href ), but doing so will make you have to Please click on the anchor tag to add space between <li>and , <a>this may not be the best solution for you.

<li 
  className="nav-item"
  key={ 3 } 
>
  <a 
    className="nav-link" 
    href="#" 
    onClick={ (e) => { e.preventDefault(); this.setActiveTab(3); } }
  >
    Edit
  </a>
</li>

But Thai Thai Duong Tran makes a good point that you don't want to recreate the function every time.

The best solution is to move your anonymous function into a class function. (This solution also removes href="#", so cursor: pointer;if you want that style, you need to add to CSS)

 <li 
   className="nav-item" 
   data-tab={ 1 }
   onClick={this.onNavClick}
 >
   <a className="nav-link">Overview</a>
 </li>

 onNavClick = (evt) => {
   this.setState({activeTab: evt.target.dataset.tab});
 }

Also, your delete has the same problem adding href="/card", so you want to be careful.

If you wish to move the route action to a different page/URL. you should consider addingreact-router

Related


How can I prevent the component from re-rendering? (with MWE)

gbewothcc How can I prevent a component from re-rendering without changing its props ? Code sandbox link/minimal working example . Move the cursor over Canvasand you can see that the console logs a number of "Canvas re-rendered"messages. At the top level, I pa

How can I prevent the component from re-rendering? (with MWE)

gbewothcc How can I prevent a component from re-rendering without changing its props ? Code sandbox link/minimal working example . Move the cursor over Canvasand you can see that the console logs a number of "Canvas re-rendered"messages. At the top level, I pa

How to prevent React from re-rendering the entire component?

center: I modified the following component definition from here as shown below. However, unlike the example, the component re-renders every time the mouse is moved over it. Re-rendering is pretty obvious: Does anyone know why this is happening? import React, {