Source: state/StateHandler.js

export class StateHandler {
    /**
     * @class StateHandler
     * @classdesc For shallowly immutable UI state handling
     */
    constructor () {
        this.stateListeners = [];
        this.state = {};
        this.stashedState = null;
    }

    /**
     * @returns {object} Current state
     */
    getState () {
        return this.state;
    }

    /**
     * @param {Object} state A new state replacing the previous one.
     */
    setState (state) {
        this.state = state;
        this.notify();
    }

    /**
     * @param {Object} props Extends and overrides keys in state.
     */
    updateState (props) {
        this.state = {
            ...this.state,
            ...props
        };
        this.notify();
    }

    /**
     * Stashes current state so it can be returned to later on.
     */
    stashCurrentState () {
        if (this.stashedState) {
            // Prevent accidentally overwriting stashed state.
            return;
        }
        this.stashedState = { ...this.state };
    }

    /**
     * To check if we have a state in stash.
     * @return {boolean} True, if we have state in stash.
     */
    hasStashedState () {
        return !!this.stashedState;
    }

    /**
     * Retain previously stashed state. Clears the stash.
     */
    useStashedState () {
        if (this.stashedState) {
            this.setState(this.stashedState);
        }
        this.stashedState = null;
    }

    /**
     * Register a listener function. Listeners will be called every time the state changes.
     * @param {function} consumer The consumer function.
     */
    addStateListener (consumer) {
        this.stateListeners.push(consumer);
    }

    /**
     * Handles calling registered listeners.
     * @private
     */
    notify () {
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        // This allows multiple small state updates to be run one after another before re-rendering is triggered
        this.timeout = setTimeout(() => {
            this.stateListeners.forEach(consumer => consumer(this.getState()));
            this.timeout = null;
        }, 10);
    }
}