import React, { Component } from "react" const R = React.createElement /** 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, componentWillReceiveProps 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) this.state = { loading: false } 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 } /** 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) }) } componentWillMount() { this._mounted = true return this._performLoad(this.props, {}) } componentWillReceiveProps(nextProps: any) { if (this.isLoadNeeded(nextProps, this.props)) { return this._performLoad(nextProps, this.props) } } componentWillUnmount() { return (this._mounted = false) } }