import { LightningElement, track } from "lwc"; /** * This is a helper class for when you want your LWC component to have state * that is automatically tracked _along with_ its previous state, in a React- * like fashion, so that you can compare current state with previous state * after a render (like React's `commponentDidUpdate`). One benefit of doing * things this way is that you can put all of your reactions to state changes * in one place, in `renderedCallback`, rather than having them in various * places throughout the component. * * The API consists in `this.prevState`, `this.state`, and `this.setState`. * * Usage: * * ``` * type MyState = { * isFetchingContent: boolean; * }; * * class MyFetchingComponent extends LightningElementWithState { * constructor() { * // `this.state` can only be initialized once * this.state = { * isFetchingContent: false * }; * } * * // Queued for execution whenever a `setState` call completes * renderedCallback() { * if (this.prevState.isFetchingContent && !this.state.isFetchingContent) { * // Do something knowing that we just finished fetching. * notifyFetchSuccessful(); * } * } * * fetchSomething() { * this.setState({ * isFetchingContent: true * }); * * fetch(whatever).then(() => { * this.setState({ * isFetching: false * } * }); * } * } * ``` */ export abstract class LightningElementWithState< T extends { [key: string]: unknown } > extends LightningElement { private _prevState = {} as T; @track private _state = {} as T; private _didInitializeState = false; protected get prevState(): T { return Object.freeze({ ...this._prevState }); } protected get state(): T { return Object.freeze({ ...this._state }); } protected set state(initialState: T) { if (!this._didInitializeState) { this._state = { ...initialState }; this._didInitializeState = true; } else { throw new Error( "To mutate state after initialization, use `this.setState`." ); } } protected setState = (state: Partial): void => { this._prevState = { ...this._state }; this._state = { ...this._state, ...state }; }; }