UNPKG

set-state-compare

Version:

setState for React that compares with the current state and only sets the state if changed.

132 lines (106 loc) 3.18 kB
import {simpleObjectValuesDifferent} from "./diff-utils.js" const settings = { mode: "queuedForceUpdate", renderComponents: [], renderLaterTimeout: undefined } const validModes = ["queuedForceUpdate", "setState"] const callRenders = () => { const renderComponents = settings.renderComponents settings.renderComponents = [] for (const renderComponent of renderComponents) { renderComponent.__shapeRender() } } export default class Shape { static setMode(newMode) { if (!validModes.includes(newMode)) { throw new Error(`Invalid mode: ${newMode}`) } settings.mode = newMode } constructor(component, data = {}) { this.__component = component this.__stateCallbacks = [] this.__renderCount = 0 this.__prevShape = {} if (settings.mode == "setState" && component.state === undefined) { component.state = {} } if (data) { this.__setDataOnThis(data, true) } } set(newData, callback) { if (simpleObjectValuesDifferent(newData, this)) { if (callback) { this.__stateCallbacks.push(callback) } this.__setDataOnThis(newData) this.__shapeRenderLater() } else if (callback) { if (this.renderPending) { // There is nothing to render because of the given new data, but a render is pending, so we need to put this in queue to call callbacks in correct order this.__stateCallbacks.push(callback) } else { // Nothing to render and not pending a render - so call callback instantly callback() } } } __setDataOnThis(newData, skipDidUpdate) { let prevShape if (this.__component.shapeUpdated && !skipDidUpdate) { prevShape = Object.assign({}, this) } for (const key in newData) { this[key] = newData[key] } if (this.__component.shapeUpdated && !skipDidUpdate) { this.__component.shapeUpdated(prevShape) } } setAsync(newData) { return new Promise((resolve, reject) => { try { this.set(newData, resolve) } catch(error) { reject(error) } }) } __shapeRender() { if (settings.mode == "setState") { this.__component.setState( {__renderCount: this.__renderCount++}, this.__shapeAfterRender ) } else { this.__component.forceUpdate(this.__shapeAfterRender) } } __shapeAfterRender = () => { this.renderPending = false this.__shapeCallCallbacks() } __shapeCallCallbacks() { for (const stateCallback of this.__stateCallbacks) { stateCallback() } if (this.__component.shapeDidUpdate) this.__component.shapeDidUpdate(this.__prevShape) this.__prevShape = Object.assign({}, this) this.__stateCallbacks = [] } __shapeRenderLater() { if (settings.renderLaterTimeout) { clearTimeout(settings.renderLaterTimeout) } const renderPosition = settings.renderComponents.indexOf(this) if (renderPosition > -1) { settings.renderComponents.splice(renderPosition, 1) } settings.renderComponents.push(this) settings.renderLaterTimeout = setTimeout(callRenders, 0) this.renderPending = true } }