diff --git a/packages/react/lib/on-change.js b/packages/react/lib/on-change.js index 87919066028..73383add6f6 100644 --- a/packages/react/lib/on-change.js +++ b/packages/react/lib/on-change.js @@ -7,6 +7,8 @@ Object.defineProperty(exports, '__esModule', { value: true }); var PATH_SEPARATOR = '.'; var TARGET = Symbol('target'); var UNSUBSCRIBE = Symbol('unsubscribe'); +var SUSPEND = Symbol('suspend'); +var RESUME = Symbol('resume'); var isPrimitive = function (value) { return value === null || (typeof value !== 'object' && typeof value !== 'function'); }; @@ -56,6 +58,7 @@ var onChange = function (object, onChange, options) { var applyPath; var applyPrevious; var isUnsubscribed = false; + var isSuspended = false; var equals = options.equals || Object.is; var propCache = new WeakMap(); var pathCache = new WeakMap(); @@ -126,8 +129,18 @@ var onChange = function (object, onChange, options) { proxyCache = null; return target; }; + var suspend = function () { + isSuspended = true; + }; + var resume = function () { + isSuspended = false; + }; var ignoreChange = function (property) { - return isUnsubscribed || (options.ignoreSymbols === true && typeof property === 'symbol'); + return ( + isUnsubscribed || + isSuspended || + (options.ignoreSymbols === true && typeof property === 'symbol') + ); }; var handler = { get: function (target, property, receiver) { @@ -137,6 +150,12 @@ var onChange = function (object, onChange, options) { if (property === UNSUBSCRIBE && pathCache.get(target) === '') { return unsubscribe(target); } + if (property === SUSPEND && pathCache.get(target) === '') { + return suspend; + } + if (property === RESUME && pathCache.get(target) === '') { + return resume; + } var value = Reflect.get(target, property, receiver); if ( isPrimitive(value) || @@ -228,5 +247,19 @@ onChange.target = function (proxy) { onChange.unsubscribe = function (proxy) { return proxy[UNSUBSCRIBE] || proxy; }; +onChange.suspend = function (proxy) { + var suspendFn = proxy[SUSPEND]; + if (suspendFn) { + suspendFn(); + } + return proxy; +}; +onChange.resume = function (proxy) { + var resumeFn = proxy[RESUME]; + if (resumeFn) { + resumeFn(); + } + return proxy; +}; module.exports = onChange; exports.default = onChange; diff --git a/packages/react/src/components/builder-component.component.tsx b/packages/react/src/components/builder-component.component.tsx index 72e3aacdd74..b1375b71997 100644 --- a/packages/react/src/components/builder-component.component.tsx +++ b/packages/react/src/components/builder-component.component.tsx @@ -568,27 +568,46 @@ export class BuilderComponent extends React.Component< case 'builder.resetState': { const { state, model } = info.data; if (model === this.name) { - for (const key in this.rootState) { - // TODO: support nested functions (somehow) - if (typeof this.rootState[key] !== 'function') { - delete this.rootState[key]; + // Suspend change tracking to batch all updates + onChange.suspend(this.rootState); + + try { + for (const key in this.rootState) { + // TODO: support nested functions (somehow) + if (typeof this.rootState[key] !== 'function') { + delete this.rootState[key]; + } } + + Object.assign(this.rootState, state); + this.setState({ + ...this.state, + state: this.rootState, + updates: ((this.state && this.state.updates) || 0) + 1, + }); + } finally { + // Resume change tracking - ensure this always runs even if deletion fails + onChange.resume(this.rootState); + this.debouncedUpdateState(); } - Object.assign(this.rootState, state); - this.setState({ - ...this.state, - state: this.rootState, - updates: ((this.state && this.state.updates) || 0) + 1, - }); } break; } case 'builder.resetSymbolState': { const { state, model, id } = info.data.state; if (this.props.builderBlock && this.props.builderBlock === id) { - for (const key in this.rootState) { - delete this.rootState[key]; + // Suspend change tracking to batch all updates + onChange.suspend(this.rootState); + + try { + for (const key in this.rootState) { + delete this.rootState[key]; + } + } finally { + // Resume change tracking - ensure this always runs even if deletion fails + onChange.resume(this.rootState); } + Object.assign(this.rootState, state); this.setState({ ...this.state, @@ -810,6 +829,8 @@ export class BuilderComponent extends React.Component< this.notifyStateChange(); }; + debouncedUpdateState = debounce(this.updateState, 1000); + get isPreviewing() { return ( (Builder.isServer || (Builder.isBrowser && Builder.isPreviewing && !this.firstLoad)) &&