cx 26.7.0 → 26.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.js CHANGED
@@ -2107,6 +2107,8 @@ class Cx extends VDOM.Component {
2107
2107
  deferCounter;
2108
2108
  pendingUpdateTimer;
2109
2109
  unsubscribeIdleRequest;
2110
+ // 0 when no synchronous setState is in flight; >0 while one is pending (re-entrancy guard for update())
2111
+ stateUpdateDepth = 0;
2110
2112
  constructor(props) {
2111
2113
  super(props);
2112
2114
  if (props.instance) {
@@ -2132,6 +2134,7 @@ class Cx extends VDOM.Component {
2132
2134
  this.unsubscribe = this.store.subscribe(this.update.bind(this));
2133
2135
  this.state.data = this.store.getData();
2134
2136
  }
2137
+ this.onStateUpdateCompleted = this.onStateUpdateCompleted.bind(this);
2135
2138
  this.flags = {};
2136
2139
  this.renderCount = 0;
2137
2140
  if (props.onError) this.componentDidCatch = this.componentDidCatchHandler.bind(this);
@@ -2163,7 +2166,7 @@ class Cx extends VDOM.Component {
2163
2166
  return this.instance;
2164
2167
  }
2165
2168
  if (this.widget && this.parentInstance)
2166
- return (this.instance = this.parentInstance.getDetachedChild(this.widget, 0, this.store));
2169
+ return (this.instance = this.parentInstance.getDetachedChild(this.widget, "0", this.store));
2167
2170
  throw new Error("Could not resolve a widget instance in the Cx component.");
2168
2171
  }
2169
2172
  render() {
@@ -2192,23 +2195,33 @@ class Cx extends VDOM.Component {
2192
2195
  let data = this.store.getData();
2193
2196
  debug(appDataFlag, data);
2194
2197
  if (this.flags.preparing) this.flags.dirty = true;
2195
- else if (isBatchingUpdates() || this.props.immediate) {
2196
- notifyBatchedUpdateStarting();
2197
- this.setState(
2198
- {
2199
- data: data,
2200
- },
2201
- notifyBatchedUpdateCompleted,
2202
- );
2198
+ // Synchronous path (while batching, or for `immediate` instances). At most one setState may be in flight
2199
+ // per Cx: if one is already pending (stateUpdateDepth != 0) skip issuing another. Re-entrant setStates
2200
+ // nested deep -- e.g. a large initial render that writes to the store while it renders -- are what trip
2201
+ // React's "Maximum update depth exceeded"; coalescing them into a single in-flight update avoids that.
2202
+ // The pending update's completion callback re-reads the store and renders again if it changed
2203
+ // (see onStateUpdateCompleted), so no change is dropped.
2204
+ else if (this.props.immediate || isBatchingUpdates()) {
2205
+ if (this.stateUpdateDepth == 0) {
2206
+ this.stateUpdateDepth = 1;
2207
+ notifyBatchedUpdateStarting();
2208
+ this.setState(
2209
+ {
2210
+ data: data,
2211
+ },
2212
+ this.onStateUpdateCompleted,
2213
+ );
2214
+ }
2203
2215
  } else {
2204
- //in standard mode sequential store commands are batched
2216
+ // standard mode: coalesce sequential store commands into a single deferred update
2205
2217
  if (!this.pendingUpdateTimer) {
2206
2218
  notifyBatchedUpdateStarting();
2207
2219
  this.pendingUpdateTimer = setTimeout(() => {
2208
2220
  delete this.pendingUpdateTimer;
2221
+ // read fresh data at fire time so the coalesced update renders the latest store state
2209
2222
  this.setState(
2210
2223
  {
2211
- data: data,
2224
+ data: this.store.getData(),
2212
2225
  },
2213
2226
  notifyBatchedUpdateCompleted,
2214
2227
  );
@@ -2216,15 +2229,38 @@ class Cx extends VDOM.Component {
2216
2229
  }
2217
2230
  }
2218
2231
  }
2232
+ // Completion callback for the synchronous setState above. If the store changed while that update was
2233
+ // rendering -- i.e. the render itself wrote to the store, as happens throughout a large initial render --
2234
+ // render once more with the latest data, keeping this callback armed; otherwise the burst has settled, so
2235
+ // clear the in-flight flag and report completion. Re-arming coalesces until the store reaches a fixpoint;
2236
+ // a genuinely non-converging update loop is still caught by React's own max-update-depth guard.
2237
+ onStateUpdateCompleted() {
2238
+ let latestData = this.store.getData();
2239
+ if (this.state.data === latestData) {
2240
+ this.stateUpdateDepth = 0;
2241
+ notifyBatchedUpdateCompleted();
2242
+ } else {
2243
+ this.stateUpdateDepth++;
2244
+ this.setState(
2245
+ {
2246
+ data: latestData,
2247
+ },
2248
+ this.onStateUpdateCompleted,
2249
+ );
2250
+ }
2251
+ }
2219
2252
  waitForIdle() {
2220
2253
  if (!this.props.deferredUntilIdle) return;
2221
2254
  if (this.unsubscribeIdleRequest) this.unsubscribeIdleRequest();
2222
2255
  let token = ++this.deferCounter;
2223
2256
  this.unsubscribeIdleRequest = onIdleCallback(
2224
2257
  () => {
2225
- this.setState({
2226
- deferToken: token,
2227
- });
2258
+ this.setState(
2259
+ {
2260
+ deferToken: token,
2261
+ },
2262
+ this.onStateUpdateCompleted,
2263
+ );
2228
2264
  },
2229
2265
  {
2230
2266
  timeout: this.props.idleTimeout || 30000,
package/dist/widgets.js CHANGED
@@ -1124,6 +1124,7 @@ class DocumentTitle extends Widget {
1124
1124
  super.explore(context, instance);
1125
1125
  }
1126
1126
  prepare(context, instance) {
1127
+ if (typeof document == "undefined") return;
1127
1128
  if (context.documentTitle.activeInstance == instance) document.title = context.documentTitle.title;
1128
1129
  }
1129
1130
  render() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cx",
3
- "version": "26.7.0",
3
+ "version": "26.7.2",
4
4
  "description": "Advanced JavaScript UI framework for admin and dashboard applications with ready to use grid, form and chart components.",
5
5
  "exports": {
6
6
  "./data": {