Reputation: 313
I am trying to learn react and am spending way too much time on something very minimal. I have 3 tabs and whichever tab is active is highlighted. The inactive tabs show a number next to them to let the user know how many items are under the tab. On page load this shows correctly, but I want these rules to apply as the tabs are clicked.
I am initiating my state like so:
this.state = {
selectedAlert: { error: false, warning: true, success: true },
}
I am rendering the tabs like so:
<li className="nav-item m-tabs__item" onClick={() => this.updateAlerts('error')}>
{ this.state.selectedAlert.error ? <span className="m-nav__link-badge m-badge m-badge--accent bg-danger">3</span> : null}
Errors
</li>
<li className="nav-item m-tabs__item" onClick={() => this.updateAlerts('warning')}>
{ this.state.selectedAlert.warning ? <span className="m-nav__link-badge m-badge m-badge--accent bg-warning">4</span> : null }
Warnings
</li>
<li className="nav-item m-tabs__item" onClick={() => this.updateAlerts('success')}>
{ this.state.selectedAlert.success ? <span className="m-nav__link-badge m-badge m-badge--accent bg-success">8</span> : null }
Acheivements
</li>
And the function is defined like so:
updateAlerts(selected){
this.setState({
selectedAlert: {error: true, warning: true, success: true,}
})
this.state.selectedAlert[selected] = false;
}
However when another tab is clicked all the notification numbers show up regardless of the tab, and when i console log the state property things get even weirder. Say I only click the second tab, now at the end of the function the first 2 values are set to false, with the tab I have not clicked yet still set to true. (even though now all 3 notifications are showing up as if they were all set to true)
Upvotes: 1
Views: 1524
Reputation: 2295
Maybe you should do a little refactoring... There's no need to keep track of all the alert types inside the state. You can simplify it a lot, and consequentially, the updateAlerts
function too.
const ERROR = 'error';
const WARNING = 'warning';
const SUCCESS = 'success';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: ERROR
};
}
render() {
const { selected } = this.state;
return (
<div>
<ul>
<li className="nav-item m-tabs__item" onClick={this.updateAlerts(ERROR)}>
{
selected !== ERROR && (
<span className="m-nav__link-badge m-badge m-badge--accent bg-danger">3</span>)
}
Errors
</li>
<li className="nav-item m-tabs__item" onClick={this.updateAlerts(WARNING)}>
{
selected !== WARNING && (
<span className="m-nav__link-badge m-badge m-badge--accent bg-warning">4</span>)
}
Warnings
</li>
<li className="nav-item m-tabs__item" onClick={this.updateAlerts(SUCCESS)}>
{
selected !== SUCCESS && (
<span className="m-nav__link-badge m-badge m-badge--accent bg-success">8</span>)
}
Achievements
</li>
</ul>
<div className="contents">
{selected === ERROR && (
<section>
ERRORS TAB ACTIVE
</section>
)}
{selected === WARNING && (
<section>
WARNING TAB ACTIVE
</section>
)}
{selected === SUCCESS && (
<section>
ACHIEVEMENTS TAB ACTIVE
</section>
)}
</div>
</div>
);
}
updateAlerts(selected) {
return () => this.setState({ selected })
}
}
ReactDOM.render(<App />, document.querySelector('main'));
ul {
border-bottom: 1px solid #444;
padding: 5px 0;
margin: 0;
}
ul li {
cursor: pointer;
display: inline-block;
margin-right: 15px;
}
ul li span {
margin-right: 5px;
}
.contents {
margin-top: 15px;
}
<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>
<main/>
Upvotes: 1
Reputation: 705
binding event is necessary to make this
work in the callback, below an example:
export default class Index extends React.Component
{
constructor()
{
this.state = {
selectedAlert: { error: false, warning: true, success: true },
}
this.updateAlerts = this.updateAlerts.bind(this);
}
updateAlerts(selected)
{
this.setState({
selectedAlert: {
error: (selected === 'error'),
warning: (selected === 'warning'),
success: (selected === 'success')
}
})
}
render()
{
return(.......)
}
}
Hope to help you!
Upvotes: 1
Reputation: 1408
The reason why your code behaves like that is because this.setState()
is asynchronous which means that this.state.selectedAlert[selected] = false
line will be executed BEFORE this.setState()
will actually set the state so this.setState()
will override the state set by this.state.selectedAlert[selected] = false
.
That is why updateAlerts
function should look like this:
updateAlerts(selected){
this.setState({
selectedAlert: {error: true, warning: true, success: true, [selected]: false}
});
}
Also, do not modify this.state
directly, always use this.setState()
. Mutating state directly can lead to bugs optimization issues. React docs say:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
And it actually happened in your code. Also, here is a good article about state mutation - https://daveceddia.com/why-not-modify-react-state-directly .
Upvotes: 2