Reputation: 2459
I'm using componentDidUpdate
to fetch data and update the component state if props have changed. This is what the React docs suggest:
This is also a good place to do network requests as long as you compare the current props to previous props
However, if I trigger a network request with an update that happens to get a faster response than one triggered by a previous update, the state will be updated with the new request and then overwritten with the older request. Which is not what I want: I want the state to be updated with the data from the request triggered by the latest update.
How can I wait for or cancel a data update in componentDidUpdate when a new update was triggered?
To make it more clear, I've written a small example to simulate the situation, you can try it out on code sandbox. There are two buttons, one changes the state immediately and the other one after 2 seconds.
Try the following behavior
Fetch B
(nothing happens)Fetch A
(data from A appears)A user however would expect Data from A
to remain, as it is the last button that was clicked.
The example code that was uploaded on CodeSandbox, in case the link breaks
import React, { Component } from "react";
import ReactDOM from "react-dom";
class Display extends Component {
constructor(props) {
super(props);
this.state = {
show: ""
};
}
componentDidUpdate(prevProps) {
if (this.props.show !== prevProps.show) {
const timeout = this.props.show === "A" ? 0 : 2000;
const show = this.props.show === "A" ? "Data from A" : "Data from B";
setTimeout(() => {
this.setState({ show });
}, timeout);
}
}
render() {
return <div>{this.state.show}</div>;
}
}
class ChangeInput extends Component {
constructor(props) {
super(props);
this.state = {
selected: ""
};
}
handleEventA = () => {
this.setState({ selected: "A" });
};
handleEventB = () => {
this.setState({ selected: "B" });
};
render() {
return (
<div>
<button type="button" onClick={this.handleEventA}>
Fetch A (fast)
</button>
<button type="button" onClick={this.handleEventB}>
Fetch B (slow)
</button>
<Display show={this.state.selected} />
</div>
);
}
}
export default ChangeInput;
const rootElement = document.getElementById("root");
ReactDOM.render(<ChangeInput />, rootElement);
I've created a diagram that shows what I suspect is happening
Upvotes: 1
Views: 409
Reputation: 12711
You can use an instance variable to save a request ID, and before updating the state check if the request ID is last one. It's more clear with code :
constructor(props) {
super(props);
this.state = {
show: ""
};
this.lastRequestId = null;
}
componentDidUpdate(prevProps) {
if (this.props.show !== prevProps.show) {
const timeout = this.props.show === "A" ? 0 : 2000;
const show = this.props.show === "A" ? "Data from A" : "Data from B";
const requestId = `REQUEST-${Date.now()}`;
this.lastRequestId = requestId;
setTimeout(() => {
if (this.lastRequestId !== requestId) {
console.log('Request canceled ...');
return;
}
this.setState({ show });
}, timeout);
}
}
Complete code : https://codesandbox.io/s/componentdidupdate-conflict-t4rfj
Upvotes: 1