How can I prevent the entire list from re-rendering when the state of one component changes?
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?
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 yournav-item
class 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