-
-
Save bvaughn/982ab689a41097237f6e9860db7ca8d6 to your computer and use it in GitHub Desktop.
// This is an example of how to fetch external data in response to updated props, | |
// If you are using an async mechanism that does not support cancellation (e.g. a Promise). | |
class ExampleComponent extends React.Component { | |
_currentId = null; | |
state = { | |
externalData: null | |
}; | |
static getDerivedStateFromProps(nextProps, prevState) { | |
// Store prevId in state so we can compare when props change. | |
// Clear out previously-loaded data (so we don't render stale stuff). | |
if (nextProps.id !== prevState.prevId) { | |
return { | |
externalData: null, | |
prevId: nextProps.id | |
}; | |
} | |
// No state update necessary | |
return null; | |
} | |
componentDidMount() { | |
this._loadAsyncData(this.props.id); | |
} | |
componentDidUpdate(prevProps, prevState) { | |
if (this.state.externalData === null) { | |
this._loadAsyncData(this.props.id); | |
} | |
} | |
componentWillUnmount() { | |
// Prevent potential setState calls after unmount, | |
// (Since these trigger DEV warnigs) | |
_currentId = null; | |
} | |
render() { | |
if (this.state.externalData === null) { | |
// Render loading state ... | |
} else { | |
// Render real UI ... | |
} | |
} | |
_loadAsyncData(id) { | |
if (id === this._currentId) { | |
// Data for this id is already loading | |
} | |
this._currentId = id; | |
asyncLoadData(id).then(externalData => { | |
// Only update state if the Promise that has just resolved, | |
// Reflects the most recently requested external data. | |
if (id === this._currentId) { | |
this.setState({ externalData }); | |
} | |
}); | |
} | |
} |
Part of the reason of getDerivedStateFromProps()
being static is that it enforces that this function stays pure, because a static method has no access to this
, thus mutations like this.setState()
are not possible.
What about if we remove static getDerivedStateFromProps method all together, and update componentDidUpdate as follow:
componentDidUpdate(prevProps){
if(this.props.id !== prevProps.id){
this.setState({externalData:null});
this._loadAsyncData(this.props.id);
}
}
A bug:
componentDidUpdate(prevProps, prevState) {
if (prevState.externalData === null) {
this._loadAsyncData(this.props.id);
}
}
must read
componentDidUpdate(prevProps, prevState) {
if (this.state.externalData === null) {
this._loadAsyncData(this.props.id);
}
}
I thought the same as @khanhlu2013. Is there a reason why you used getDerivedStateFromProps
instead of comparing the IDs (this.props.id !== prevProps.id
) in componentDidUpdate
?
shouldn't asyncLoadData catch error?
asyncLoadData(id).then(externalData => {
// Only update state if the Promise that has just resolved,
// Reflects the most recently requested external data.
if (id === this._currentId) {
this.setState({ externalData });
}
});
should be
asyncLoadData(id).then(externalData => {
// Only update state if the Promise that has just resolved,
// Reflects the most recently requested external data.
if (id === this._currentId) {
this.setState({ externalData });
}
}).catch(() => this._currentId = null);
akonsu's change is required.
inomdzhon's change is required.
If the author isn't going to update the gist, could someone else create a new one, along with a online running example?
@Stephen-ONeil I feel the same way, if (id === this._currentId) {
is basically just an isMounted check ...
A bug:
componentDidUpdate(prevProps, prevState) { if (prevState.externalData === null) { this._loadAsyncData(this.props.id); } }
must read
componentDidUpdate(prevProps, prevState) { if (this.state.externalData === null) { this._loadAsyncData(this.props.id); } }
Yes, This will cause the second render to fail. Because prevState
is having old data now.
Blog post have the correct code
Thanks for pointing out the typo. I often don't notice comments on Gists. I've updated the example.
To put it another way, I feel like we both effectively re-invented isMounted(), meaning patterns like this have the same pitfalls laid out in the first couple paragraphs of this post.