React setState Reads Its Own State
ID |
javascript.react_setstate_previous_state |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
react, reliability, state |
Description
Reports the object form of this.setState computing a key from the current value of that same key.
Only a key computed from itself is reported: an entry that reads a different slot than the one it writes cannot lose an update to batching, and an entry that does not read state at all is not considered. The reported node is the offending entry, so a literal that sets several keys names the one that is wrong.
Rationale
import React from "react";
class Counter extends React.Component {
// Bad — two increments in one handler produce one
bump() {
this.setState({ count: this.state.count + 1 });
}
}
-
setStatedoes not apply the update where it is written: React batches the calls of one event and applies them together.this.statestill holds the value from before the batch. -
Two increments in the same handler — or two components bumping the same counter in response to one event — therefore produce one increment. Nothing fails; the number is simply lower than it should be, occasionally, under load.
Remediation
Use the updater form. React hands it the state as it stands when that particular update is applied, which is the value the computation needs.
import React from "react";
class Counter extends React.Component {
// Good
bump() {
this.setState(previous => ({ count: previous.count + 1 }));
}
// Good — props are not batched state, so reading them directly is fine
sync() {
this.setState({ step: this.props.step });
}
}