import React, { Component } from "react" /** React component that asynchronously loads something into state from the props * Handles the common case of wanting to load something but having to deal with the complexities * of multiple updates, unmounting, componentDidUpdate vs componentDidMount, etc. * To use, override isLoadNeeded to determine if a prop change requires a load * and load to perform load and call setState with callback value. * Sets state of loading to true/false appropriately (automatically part of state) * DO NOT call setState or reference props in load */ export default abstract class AsyncLoadComponent
extends Component
{
_mounted: boolean
_loadSeqStarted: number
_loadSeqCompleted: number
constructor(props: P) {
super(props)
// Start in loading state since componentDidMount will trigger a load before the next render.
// Note: subclasses commonly overwrite `this.state` in their own constructor, which would wipe
// this out. `getDerivedStateFromProps` below re-asserts it before the first render.
this.state = {
loading: true
} as S
// Keep track if mounted
this._mounted = false
// Keep track of load number started and completed to ignore old ones
this._loadSeqStarted = 0
this._loadSeqCompleted = 0
}
// Ensure `loading` defaults to true on the first render even if a subclass constructor
// overwrote `this.state` without including it. Runs after constructor, before first render.
static getDerivedStateFromProps(props: any, state: any) {
if (state.loading === undefined) {
return { loading: true }
}
return null
}
/** Check if mid-loading */
isLoading = () => {
return this.state.loading
}
/** Override to determine if a load is needed. Not called on mounting */
abstract isLoadNeeded(newProps: P, oldProps: P): boolean
/** Call callback with state changes */
abstract load(props: P, prevProps: P, callback: (stateUpdate: Partial) => void): void
/** Call to force load */
forceLoad(): void {
this._performLoad(this.props, this.props)
}
_performLoad(newProps: any, oldProps: any) {
this._loadSeqStarted += 1
const seq = this._loadSeqStarted
// Create callback
const callback = (state: any) => {
// Check if unmounted
if (!this._mounted) {
return
}
// Check if out of date
if (seq < this._loadSeqCompleted) {
return
}
this._loadSeqCompleted = seq
// Apply state
this.setState(state)
// Check if latest
if (seq === this._loadSeqStarted) {
return this.setState({ loading: false })
}
}
return this.setState({ loading: true }, () => {
return this.load(newProps, oldProps, callback)
})
}
componentDidMount() {
this._mounted = true
return this._performLoad(this.props, {} as P)
}
componentDidUpdate(prevProps: P) {
if (this.isLoadNeeded(this.props, prevProps)) {
return this._performLoad(this.props, prevProps)
}
}
componentWillUnmount() {
return (this._mounted = false)
}
}