I have a list component that fetches data and maps the results over card-like child components to create a list of "cards". Below is a 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, clicking a tab determine which text is shown in each particular card. The tab/toggle is working, but my issue is that the entire list is re-rendering when the tab is clicked. So if you are at the bottom of the list, the re-render brings you back to the top of the list.
I've tried implementing componentShouldUpdate, but with no success thus far. I would like for the card to toggle it's content and the list remain in place. Is using the shouldUpdate lifecycle method the correct route? Or is there a better approach?
I think it's the prbolem
onClick={ () => this.setActiveTab(2) }
Essentially, React
decides to re-render a component when it detects a change
in component state or props, and it only does shallow comparison for object.
As you are using closures, these function is dynamically re-created every time the Card
component render function is called. React detects a new object (in JS, function is treated as object) passed to the li
element, then it redraws it.
To solve this problem you can do something like:
<li className="nav-item" data-tab={ 1 }
onClick={this.navLinkClickHandler}>
<a className="nav-link" href="#">Overview</a>
</li>
Then have navLinkClickHandler
defined in your component:
navLinkClickHandler = (evt) => this.setState({activeTab: evt.target.dataset.tab})
More on React reconcilation can be found here:
https://reactjs.org/docs/reconciliation.html
And about using closure in render function:
https://reda-bx.gitbooks.io/react-bits/content/conventions/08.closures-in-render.html
In your <a>
anchor tag you have href="#" ` which will always re-direct you to a new link. (# will re-direct to current page re-rendering everything).
Easiest Solution is to remove href="#" which will remove your
cursor: pointer;
styling but you can re-add that back into yournav-item
classname.
Another simple solution is you can move the onClick into the anchor tag and adding evt.preventDefault() (Which prevents the event handler from doing the default action which is loading the page to the href) However this will make it so that you have to click the anchor tag so if you have padding between the <li>
and <a>
this might not be your best solution.
<li
className="nav-item"
key={ 3 }
>
<a
className="nav-link"
href="#"
onClick={ (e) => { e.preventDefault(); this.setActiveTab(3); } }
>
Edit
</a>
</li>
However Thai Duong Tran made a good point where you don't want to re-create the function every time.
Best solution is to move your anonymous function into a class function. (This solution also removes the href="#" so you need to add
cursor: pointer;
into your css if you want that style)
<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 action has the same issue where you added a href="/card" so you want to be careful with that.
If you want to have route actions to move to different pages/URL. You should look into adding react-router
Try this code inside CARD component (If you have not tried it already). Might work fine.
shouldComponentUpdate(nextProps,nextState){
if(this.state.activeTab === nextState.activeTab ){
return false
} else {
return true
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.