Last active
July 9, 2020 08:34
-
-
Save loopmode/f42c816994edaa5404b8ca5763667f26 to your computer and use it in GitHub Desktop.
Safeguarding async chains in React
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// we have an async "initialize" method that performs several calls consecutively. | |
// after each step in the async chain, we might have been unmounted already | |
// and performing any further calls becomes obsolete | |
class DefaultComponent extends React.Component { | |
// ... | |
componentDidMount() { | |
this._isMounted = true; | |
this.initialize(); | |
} | |
componentWillUnmount() { | |
this._isMounted = false; | |
} | |
// ... | |
// note how we need to check or exit before/after each | |
async initialize() { | |
this.setState({ isLoading: true }); | |
const a = await foo(); | |
if (!this._isMounted) return; | |
const b = await bar(); | |
if (!this._isMounted) return; | |
const c = await baz(); | |
if (!this._isMounted) return; | |
// instead of return, we might also only execute if still mounted: | |
let d; | |
if (this._isMounted) { | |
d = await boo(); | |
} | |
// we might use boolean logic syntax | |
this._isMounted && this.setState({ a, b, c, d, isLoading: false }); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here we use a "invoke" method that does nothing if we aren't mounted anymore | |
// Note how the code in "initialize" has become much more readable | |
class GuardedComponent extends React.Component { | |
// ... | |
componentDidMount() { | |
this._isMounted = true; | |
this.initialize(); | |
} | |
componentWillUnmount() { | |
this._isMounted = false; | |
} | |
invoke(fn) { | |
if (this._isMounted) { | |
return fn(); | |
} | |
} | |
// ... | |
async initialize() { | |
this.setState({ isLoading: true }); | |
const a = await this.invoke(() => foo()); | |
const b = await this.invoke(() => bar()); | |
const c = await this.invoke(() => baz()); | |
const d = await this.invoke(() => boo()); | |
this.invoke(() => this.setState({ a, b, c, d, isLoading: false })) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// you can override setState and only call super.setState when still mounted. | |
// that way, you don't have to care when you call setState - you'll never perform noops due to being unmounted | |
class GuardedSetState extends React.Component { | |
// ... | |
setState(nextState, cb) { | |
if (this._isMounted) { | |
super.setState(nextState, cb); | |
} | |
} | |
// ... | |
async initialize() { | |
this.setState({ isLoading: true }); | |
const result = await somethingVerySlow(); | |
this.setState({ result, isLoading: false }); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here we override setState and return a promise. | |
// That way we can actually await setState calls. | |
// (While this isn't something you should do carelessly as a newcomer, | |
// there actually are situations where this technique is useful. | |
// Just keep in mind that the render method will definitely be called before your async chain proceeds) | |
class PromisedSetState extends React.Component { | |
// ... | |
setState(nextState, cb) { | |
if (this._isMounted) { | |
return new Promise(resolve => { | |
super.setState(nextState, () => { | |
if (cb) cb(); | |
resolve(); | |
}); | |
}); | |
} | |
} | |
// ... | |
async initialize() { | |
const someValue = await foo(); | |
await this.setState({ someValue }); // <- we wait for this.state.someValue to be applied | |
const b = await this.bar(); // <- because this.bar() might depend on the value | |
await this.setState({ b }); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment