Skip to content

Instantly share code, notes, and snippets.

@loopmode
Last active July 9, 2020 08:34
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save loopmode/f42c816994edaa5404b8ca5763667f26 to your computer and use it in GitHub Desktop.
Save loopmode/f42c816994edaa5404b8ca5763667f26 to your computer and use it in GitHub Desktop.
Safeguarding async chains in React
// 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 });
}
}
// 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 }))
}
}
// 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 });
}
}
// 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