cx 26.7.1 → 26.7.3
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/build/ui/Cx.d.ts +3 -5
- package/build/ui/Cx.d.ts.map +1 -1
- package/build/ui/Cx.js +48 -44
- package/build/ui/Restate.d.ts +0 -6
- package/build/ui/Restate.d.ts.map +1 -1
- package/build/ui/Restate.js +1 -2
- package/dist/manifest.js +784 -784
- package/dist/ui.js +66 -58
- package/package.json +1 -1
- package/src/ui/Cx.tsx +422 -427
- package/src/ui/Restate.tsx +217 -226
package/dist/ui.js
CHANGED
|
@@ -2094,30 +2094,6 @@ class LinkedListsNode {
|
|
|
2094
2094
|
}
|
|
2095
2095
|
}
|
|
2096
2096
|
|
|
2097
|
-
// Optional guard (opt in per-instance via the `limitSyncBursts` prop) against React's "Maximum update depth
|
|
2098
|
-
// exceeded" during very large synchronous render bursts. On the initial render of a large document the store
|
|
2099
|
-
// can be mutated thousands of times within a single React commit: every Instance.setState wraps store.notify()
|
|
2100
|
-
// in batchUpdates(), so isBatchingUpdates() stays true and a subscribed Cx takes its *synchronous* setState
|
|
2101
|
-
// branch on each notification. React counts those as nested render-phase updates and aborts past ~50
|
|
2102
|
-
// (surfacing as the recoverable "error during concurrent rendering"). When a Cx opts in, once back-to-back
|
|
2103
|
-
// updates exceed the limit it falls back to the coalesced setTimeout branch, which renders the latest store
|
|
2104
|
-
// data on the next tick. The counter is global (it mirrors React's global nested-update counter) and resets
|
|
2105
|
-
// after any idle gap. It is advanced on every update() of an opted-in Cx -- counting only the synchronous
|
|
2106
|
-
// branch would let the window reset whenever a deferred update lands between two synchronous ones, so the
|
|
2107
|
-
// counter would never reach the limit. performance.now() gives sub-ms resolution (Date.now() is the fallback;
|
|
2108
|
-
// Timing.now() can't be used here because it returns 0 in production).
|
|
2109
|
-
const SYNC_UPDATE_BURST_LIMIT = 30;
|
|
2110
|
-
const SYNC_UPDATE_BURST_RESET_MS = 10;
|
|
2111
|
-
const syncUpdateBurstNow =
|
|
2112
|
-
typeof performance !== "undefined" && performance.now ? () => performance.now() : () => Date.now();
|
|
2113
|
-
let syncUpdateBurstCount = 0;
|
|
2114
|
-
let syncUpdateBurstLastAt = 0;
|
|
2115
|
-
function trackSyncUpdateBurst() {
|
|
2116
|
-
let t = syncUpdateBurstNow();
|
|
2117
|
-
if (t - syncUpdateBurstLastAt > SYNC_UPDATE_BURST_RESET_MS) syncUpdateBurstCount = 0;
|
|
2118
|
-
syncUpdateBurstLastAt = t;
|
|
2119
|
-
return ++syncUpdateBurstCount > SYNC_UPDATE_BURST_LIMIT;
|
|
2120
|
-
}
|
|
2121
2097
|
class Cx extends VDOM.Component {
|
|
2122
2098
|
widget;
|
|
2123
2099
|
store;
|
|
@@ -2131,6 +2107,8 @@ class Cx extends VDOM.Component {
|
|
|
2131
2107
|
deferCounter;
|
|
2132
2108
|
pendingUpdateTimer;
|
|
2133
2109
|
unsubscribeIdleRequest;
|
|
2110
|
+
// 0 when no synchronous setState is in flight; >0 while one is pending (re-entrancy guard for update())
|
|
2111
|
+
stateUpdateDepth = 0;
|
|
2134
2112
|
constructor(props) {
|
|
2135
2113
|
super(props);
|
|
2136
2114
|
if (props.instance) {
|
|
@@ -2151,11 +2129,12 @@ class Cx extends VDOM.Component {
|
|
|
2151
2129
|
}
|
|
2152
2130
|
this.state = {
|
|
2153
2131
|
deferToken: 0,
|
|
2132
|
+
data: props.subscribe ? this.store.getData() : null,
|
|
2154
2133
|
};
|
|
2155
2134
|
if (props.subscribe) {
|
|
2156
2135
|
this.unsubscribe = this.store.subscribe(this.update.bind(this));
|
|
2157
|
-
this.state.data = this.store.getData();
|
|
2158
2136
|
}
|
|
2137
|
+
this.onStateUpdateCompleted = this.onStateUpdateCompleted.bind(this);
|
|
2159
2138
|
this.flags = {};
|
|
2160
2139
|
this.renderCount = 0;
|
|
2161
2140
|
if (props.onError) this.componentDidCatch = this.componentDidCatchHandler.bind(this);
|
|
@@ -2187,7 +2166,7 @@ class Cx extends VDOM.Component {
|
|
|
2187
2166
|
return this.instance;
|
|
2188
2167
|
}
|
|
2189
2168
|
if (this.widget && this.parentInstance)
|
|
2190
|
-
return (this.instance = this.parentInstance.getDetachedChild(this.widget, 0, this.store));
|
|
2169
|
+
return (this.instance = this.parentInstance.getDetachedChild(this.widget, "0", this.store));
|
|
2191
2170
|
throw new Error("Could not resolve a widget instance in the Cx component.");
|
|
2192
2171
|
}
|
|
2193
2172
|
render() {
|
|
@@ -2216,35 +2195,63 @@ class Cx extends VDOM.Component {
|
|
|
2216
2195
|
let data = this.store.getData();
|
|
2217
2196
|
debug(appDataFlag, data);
|
|
2218
2197
|
if (this.flags.preparing) this.flags.dirty = true;
|
|
2219
|
-
//
|
|
2220
|
-
//
|
|
2221
|
-
//
|
|
2222
|
-
//
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
},
|
|
2229
|
-
notifyBatchedUpdateCompleted,
|
|
2230
|
-
);
|
|
2231
|
-
} else {
|
|
2232
|
-
//in standard mode sequential store commands are batched -- as is any update that exceeds the
|
|
2233
|
-
//synchronous burst limit above, which falls through here to avoid React's max-update-depth abort
|
|
2234
|
-
if (!this.pendingUpdateTimer) {
|
|
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;
|
|
2235
2207
|
notifyBatchedUpdateStarting();
|
|
2236
|
-
this.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
this.
|
|
2241
|
-
|
|
2242
|
-
data: this.props.limitSyncBursts ? this.store.getData() : data,
|
|
2243
|
-
},
|
|
2244
|
-
notifyBatchedUpdateCompleted,
|
|
2245
|
-
);
|
|
2246
|
-
}, 0);
|
|
2208
|
+
this.setState(
|
|
2209
|
+
{
|
|
2210
|
+
data: data,
|
|
2211
|
+
},
|
|
2212
|
+
this.onStateUpdateCompleted,
|
|
2213
|
+
);
|
|
2247
2214
|
}
|
|
2215
|
+
} else {
|
|
2216
|
+
// standard mode: coalesce sequential store commands into a single deferred update
|
|
2217
|
+
this.scheduleStateUpdate();
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
scheduleStateUpdate() {
|
|
2221
|
+
if (!this.pendingUpdateTimer) {
|
|
2222
|
+
notifyBatchedUpdateStarting();
|
|
2223
|
+
this.pendingUpdateTimer = setTimeout(() => {
|
|
2224
|
+
delete this.pendingUpdateTimer;
|
|
2225
|
+
// read fresh data at fire time so the coalesced update renders the latest store state
|
|
2226
|
+
this.setState(
|
|
2227
|
+
{
|
|
2228
|
+
data: this.store.getData(),
|
|
2229
|
+
},
|
|
2230
|
+
notifyBatchedUpdateCompleted,
|
|
2231
|
+
);
|
|
2232
|
+
}, 0);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
// Completion callback for the synchronous setState above. If the store changed while that update was
|
|
2236
|
+
// rendering -- i.e. the render itself wrote to the store, as happens throughout a large initial render --
|
|
2237
|
+
// render once more with the latest data, keeping this callback armed; otherwise the burst has settled, so
|
|
2238
|
+
// clear the in-flight flag and report completion. Re-arming coalesces until the store reaches a fixpoint;
|
|
2239
|
+
// a genuinely non-converging update loop is still caught by React's own max-update-depth guard.
|
|
2240
|
+
onStateUpdateCompleted() {
|
|
2241
|
+
let latestData = this.store.getData();
|
|
2242
|
+
notifyBatchedUpdateCompleted();
|
|
2243
|
+
if (this.state.data === latestData) {
|
|
2244
|
+
this.stateUpdateDepth = 0;
|
|
2245
|
+
} else {
|
|
2246
|
+
this.stateUpdateDepth++;
|
|
2247
|
+
if (this.stateUpdateDepth < 20 && isBatchingUpdates())
|
|
2248
|
+
this.setState(
|
|
2249
|
+
{
|
|
2250
|
+
data: latestData,
|
|
2251
|
+
},
|
|
2252
|
+
this.onStateUpdateCompleted,
|
|
2253
|
+
);
|
|
2254
|
+
else this.scheduleStateUpdate();
|
|
2248
2255
|
}
|
|
2249
2256
|
}
|
|
2250
2257
|
waitForIdle() {
|
|
@@ -2253,9 +2260,12 @@ class Cx extends VDOM.Component {
|
|
|
2253
2260
|
let token = ++this.deferCounter;
|
|
2254
2261
|
this.unsubscribeIdleRequest = onIdleCallback(
|
|
2255
2262
|
() => {
|
|
2256
|
-
this.setState(
|
|
2257
|
-
|
|
2258
|
-
|
|
2263
|
+
this.setState(
|
|
2264
|
+
{
|
|
2265
|
+
deferToken: token,
|
|
2266
|
+
},
|
|
2267
|
+
this.onStateUpdateCompleted,
|
|
2268
|
+
);
|
|
2259
2269
|
},
|
|
2260
2270
|
{
|
|
2261
2271
|
timeout: this.props.idleTimeout || 30000,
|
|
@@ -2693,7 +2703,6 @@ class Restate extends PureContainerBase {
|
|
|
2693
2703
|
deferredUntilIdle: instance.data.deferredUntilIdle,
|
|
2694
2704
|
idleTimeout: instance.data.idleTimeout,
|
|
2695
2705
|
immediate: this.immediate,
|
|
2696
|
-
limitSyncBursts: this.limitSyncBursts,
|
|
2697
2706
|
cultureInfo: instance.cultureInfo,
|
|
2698
2707
|
},
|
|
2699
2708
|
key,
|
|
@@ -2703,7 +2712,6 @@ class Restate extends PureContainerBase {
|
|
|
2703
2712
|
Restate.prototype.detached = false;
|
|
2704
2713
|
Restate.prototype.waitForIdle = false;
|
|
2705
2714
|
Restate.prototype.immediate = false;
|
|
2706
|
-
Restate.prototype.limitSyncBursts = false;
|
|
2707
2715
|
Restate.prototype.culture = null;
|
|
2708
2716
|
const PrivateStore = Restate;
|
|
2709
2717
|
class RestateStore extends Store {
|