From 1933262f2182dbdd84f92111489295a08a99e1a9 Mon Sep 17 00:00:00 2001 From: Midhun A Darvin Date: Thu, 30 Oct 2025 16:53:54 +0530 Subject: [PATCH 1/3] feat: optimize re-renders for builder-component --- packages/react/lib/on-change.js | 35 ++++++++++++++++++- .../builder-component.component.tsx | 13 +++++++ 2 files changed, 47 insertions(+), 1 deletion(-) 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..8ed2fea27fe 100644 --- a/packages/react/src/components/builder-component.component.tsx +++ b/packages/react/src/components/builder-component.component.tsx @@ -568,12 +568,18 @@ export class BuilderComponent extends React.Component< case 'builder.resetState': { const { state, model } = info.data; if (model === this.name) { + // Suspend change tracking to batch all updates + onChange.suspend(this.rootState); + for (const key in this.rootState) { // TODO: support nested functions (somehow) if (typeof this.rootState[key] !== 'function') { delete this.rootState[key]; } } + // Resume change tracking + onChange.resume(this.rootState); + Object.assign(this.rootState, state); this.setState({ ...this.state, @@ -586,9 +592,16 @@ export class BuilderComponent extends React.Component< case 'builder.resetSymbolState': { const { state, model, id } = info.data.state; if (this.props.builderBlock && this.props.builderBlock === id) { + // Suspend change tracking to batch all updates + onChange.suspend(this.rootState); + for (const key in this.rootState) { delete this.rootState[key]; } + + // Resume change tracking + onChange.resume(this.rootState); + Object.assign(this.rootState, state); this.setState({ ...this.state, From b4890266a60f4d49c58ef5b104ec939a56162968 Mon Sep 17 00:00:00 2001 From: Midhun A Darvin Date: Thu, 30 Oct 2025 17:04:10 +0530 Subject: [PATCH 2/3] fix: review comment --- .../builder-component.component.tsx | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react/src/components/builder-component.component.tsx b/packages/react/src/components/builder-component.component.tsx index 8ed2fea27fe..9bf101966b7 100644 --- a/packages/react/src/components/builder-component.component.tsx +++ b/packages/react/src/components/builder-component.component.tsx @@ -571,14 +571,17 @@ export class BuilderComponent extends React.Component< // Suspend change tracking to batch all updates onChange.suspend(this.rootState); - for (const key in this.rootState) { - // TODO: support nested functions (somehow) - if (typeof this.rootState[key] !== 'function') { - delete this.rootState[key]; + try { + for (const key in this.rootState) { + // TODO: support nested functions (somehow) + if (typeof this.rootState[key] !== 'function') { + delete this.rootState[key]; + } } + } finally { + // Resume change tracking - ensure this always runs even if deletion fails + onChange.resume(this.rootState); } - // Resume change tracking - onChange.resume(this.rootState); Object.assign(this.rootState, state); this.setState({ @@ -595,13 +598,15 @@ export class BuilderComponent extends React.Component< // Suspend change tracking to batch all updates onChange.suspend(this.rootState); - for (const key in this.rootState) { - delete this.rootState[key]; + 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); } - // Resume change tracking - onChange.resume(this.rootState); - Object.assign(this.rootState, state); this.setState({ ...this.state, From 316c80d6d4a51be6f70c90cf0f2a8e8850a1a6b3 Mon Sep 17 00:00:00 2001 From: Midhun A Darvin Date: Fri, 31 Oct 2025 12:16:51 +0530 Subject: [PATCH 3/3] fix: improvements --- .../components/builder-component.component.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/builder-component.component.tsx b/packages/react/src/components/builder-component.component.tsx index 9bf101966b7..b1375b71997 100644 --- a/packages/react/src/components/builder-component.component.tsx +++ b/packages/react/src/components/builder-component.component.tsx @@ -578,17 +578,18 @@ export class BuilderComponent extends React.Component< 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; } @@ -828,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)) &&