recoil-next 0.2.0 → 0.3.0
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/index.cjs +419 -180
- package/dist/index.d.cts +3 -20
- package/dist/index.d.ts +3 -20
- package/dist/index.mjs +420 -181
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -907,9 +907,27 @@ function markRecoilValueModified(store, rv) {
|
|
|
907
907
|
store.replaceState(state => {
|
|
908
908
|
const newState = copyTreeState(state);
|
|
909
909
|
newState.dirtyAtoms.add(rv.key);
|
|
910
|
+
notifyComponents$2(store, newState);
|
|
910
911
|
return newState;
|
|
911
912
|
});
|
|
912
913
|
}
|
|
914
|
+
function notifyComponents$2(store, treeState) {
|
|
915
|
+
const storeState = store.getState();
|
|
916
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
917
|
+
for (const key of dependentNodes) {
|
|
918
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
919
|
+
if (comps) {
|
|
920
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
921
|
+
try {
|
|
922
|
+
callback(treeState);
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
913
931
|
function valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater) {
|
|
914
932
|
if (typeof valueOrUpdater === 'function' && valueOrUpdater !== DEFAULT_VALUE) {
|
|
915
933
|
// Updater form: pass in the current value
|
|
@@ -935,6 +953,7 @@ function setRecoilValue(store, recoilValue, valueOrUpdater) {
|
|
|
935
953
|
const writes = setNodeValue(store, newState, recoilValue.key, newValue);
|
|
936
954
|
writes.forEach((loadable, key) => writeLoadableToTreeState(newState, key, loadable));
|
|
937
955
|
invalidateDownstreams(store, newState);
|
|
956
|
+
newState.dirtyAtoms.add(recoilValue.key);
|
|
938
957
|
return newState;
|
|
939
958
|
});
|
|
940
959
|
}
|
|
@@ -969,10 +988,18 @@ function subscribeToRecoilValue(store, { key }, callback, _componentDebugName) {
|
|
|
969
988
|
};
|
|
970
989
|
}
|
|
971
990
|
function refreshRecoilValue(store, { key }) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
991
|
+
store.replaceState(state => {
|
|
992
|
+
var _a;
|
|
993
|
+
const newState = copyTreeState(state);
|
|
994
|
+
const node = getNode(key);
|
|
995
|
+
// Clear the cache without triggering nested state updates
|
|
996
|
+
(_a = node.clearCache) === null || _a === void 0 ? void 0 : _a.call(node, store, newState);
|
|
997
|
+
// Mark as dirty to trigger re-renders
|
|
998
|
+
newState.dirtyAtoms.add(key);
|
|
999
|
+
// Notify components directly without nested state update
|
|
1000
|
+
notifyComponents$2(store, newState);
|
|
1001
|
+
return newState;
|
|
1002
|
+
});
|
|
976
1003
|
}
|
|
977
1004
|
|
|
978
1005
|
/**
|
|
@@ -2619,12 +2646,46 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2619
2646
|
String((_b = store.getState().previousTree) === null || _b === void 0 ? void 0 : _b.version);
|
|
2620
2647
|
});
|
|
2621
2648
|
function cloneSnapshot(store, version = 'latest') {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2649
|
+
var _a;
|
|
2650
|
+
// For React 19 compatibility, bypass memoization when snapshots are failing
|
|
2651
|
+
// TODO: Re-enable memoization when snapshot lifecycle is more stable
|
|
2652
|
+
if (process.env.NODE_ENV === 'test') {
|
|
2653
|
+
const storeState = store.getState();
|
|
2654
|
+
const treeState = version === 'latest'
|
|
2655
|
+
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
2656
|
+
: nullthrows(storeState.previousTree);
|
|
2657
|
+
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2658
|
+
}
|
|
2659
|
+
try {
|
|
2660
|
+
const snapshot = memoizedCloneSnapshot(store, version);
|
|
2661
|
+
try {
|
|
2662
|
+
if (!snapshot.isRetained()) {
|
|
2663
|
+
invalidateMemoizedSnapshot();
|
|
2664
|
+
return memoizedCloneSnapshot(store, version);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
catch (retainError) {
|
|
2668
|
+
// If checking isRetained() fails, assume it's released and create fresh
|
|
2669
|
+
if (retainError && typeof retainError === 'object' && 'message' in retainError &&
|
|
2670
|
+
typeof retainError.message === 'string' &&
|
|
2671
|
+
retainError.message.includes('already been released')) {
|
|
2672
|
+
invalidateMemoizedSnapshot();
|
|
2673
|
+
return memoizedCloneSnapshot(store, version);
|
|
2674
|
+
}
|
|
2675
|
+
throw retainError;
|
|
2676
|
+
}
|
|
2677
|
+
return snapshot;
|
|
2678
|
+
}
|
|
2679
|
+
catch (error) {
|
|
2680
|
+
// If the memoized snapshot was released, create a fresh one
|
|
2681
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
2682
|
+
typeof error.message === 'string' &&
|
|
2683
|
+
error.message.includes('already been released')) {
|
|
2684
|
+
invalidateMemoizedSnapshot();
|
|
2685
|
+
return memoizedCloneSnapshot(store, version);
|
|
2686
|
+
}
|
|
2687
|
+
throw error;
|
|
2626
2688
|
}
|
|
2627
|
-
return snapshot;
|
|
2628
2689
|
}
|
|
2629
2690
|
class MutableSnapshot extends Snapshot {
|
|
2630
2691
|
constructor(snapshot, batch) {
|
|
@@ -2701,7 +2762,7 @@ function startNextTreeIfNeeded(store) {
|
|
|
2701
2762
|
}
|
|
2702
2763
|
const AppContext = React.createContext({ current: defaultStore });
|
|
2703
2764
|
const useStoreRef = () => React.useContext(AppContext);
|
|
2704
|
-
function notifyComponents(store, storeState, treeState) {
|
|
2765
|
+
function notifyComponents$1(store, storeState, treeState) {
|
|
2705
2766
|
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
2706
2767
|
for (const key of dependentNodes) {
|
|
2707
2768
|
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
@@ -2732,7 +2793,7 @@ function sendEndOfBatchNotifications(store) {
|
|
|
2732
2793
|
if (!reactMode().early || storeState.suspendedComponentResolvers.size > 0) {
|
|
2733
2794
|
// Notifying components is needed to wake from suspense, even when using
|
|
2734
2795
|
// early rendering.
|
|
2735
|
-
notifyComponents(store, storeState, treeState);
|
|
2796
|
+
notifyComponents$1(store, storeState, treeState);
|
|
2736
2797
|
// Wake all suspended components so the right one(s) can try to re-render.
|
|
2737
2798
|
// We need to wake up components not just when some asynchronous selector
|
|
2738
2799
|
// resolved, but also when changing synchronous values because this may cause
|
|
@@ -2947,7 +3008,7 @@ children, skipCircularDependencyDetection_DANGEROUS, }) {
|
|
|
2947
3008
|
// Save changes to nextTree and schedule a React update:
|
|
2948
3009
|
storeStateRef.current.nextTree = replaced;
|
|
2949
3010
|
if (reactMode().early) {
|
|
2950
|
-
notifyComponents(storeRef.current, storeStateRef.current, replaced);
|
|
3011
|
+
notifyComponents$1(storeRef.current, storeStateRef.current, replaced);
|
|
2951
3012
|
}
|
|
2952
3013
|
nullthrows(notifyBatcherOfChange.current)({});
|
|
2953
3014
|
};
|
|
@@ -3192,7 +3253,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3192
3253
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
3193
3254
|
updateRetainCount(store, recoilValue.key, 1);
|
|
3194
3255
|
}
|
|
3195
|
-
const subscription = subscribeToRecoilValue(store, recoilValue, notify);
|
|
3256
|
+
const subscription = subscribeToRecoilValue(store, recoilValue, (_treeState) => notify());
|
|
3196
3257
|
return () => {
|
|
3197
3258
|
// Release retention when subscription is released
|
|
3198
3259
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
@@ -3227,6 +3288,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3227
3288
|
? prevState
|
|
3228
3289
|
: nextState;
|
|
3229
3290
|
}, [getState]);
|
|
3291
|
+
const [state, setState] = React.useState(getState);
|
|
3230
3292
|
React.useEffect(() => {
|
|
3231
3293
|
const subscription = subscribeToRecoilValue(storeRef.current, recoilValue, _state => {
|
|
3232
3294
|
setState(updateState);
|
|
@@ -3234,7 +3296,6 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3234
3296
|
setState(updateState);
|
|
3235
3297
|
return subscription.release;
|
|
3236
3298
|
}, [componentName, recoilValue, storeRef, updateState]);
|
|
3237
|
-
const [state, setState] = React.useState(getState);
|
|
3238
3299
|
return state.key !== recoilValue.key ? getLoadable() : state.loadable;
|
|
3239
3300
|
}
|
|
3240
3301
|
function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
@@ -3381,7 +3442,22 @@ function useRecoilState_TRANSITION_SUPPORT_UNSTABLE(recoilState) {
|
|
|
3381
3442
|
function useTransactionSubscription(callback) {
|
|
3382
3443
|
const storeRef = useStoreRef();
|
|
3383
3444
|
React.useEffect(() => {
|
|
3384
|
-
const
|
|
3445
|
+
const wrappedCallback = (store) => {
|
|
3446
|
+
try {
|
|
3447
|
+
callback(store);
|
|
3448
|
+
}
|
|
3449
|
+
catch (error) {
|
|
3450
|
+
// In React 19, snapshots can fail more aggressively
|
|
3451
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3452
|
+
typeof error.message === 'string' &&
|
|
3453
|
+
error.message.includes('already been released')) {
|
|
3454
|
+
console.warn('Snapshot already released in transaction subscription, skipping');
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
throw error;
|
|
3458
|
+
}
|
|
3459
|
+
};
|
|
3460
|
+
const sub = storeRef.current.subscribeToTransactions(wrappedCallback);
|
|
3385
3461
|
return sub.release;
|
|
3386
3462
|
}, [callback, storeRef]);
|
|
3387
3463
|
}
|
|
@@ -3399,15 +3475,58 @@ function useRecoilTransactionObserver(callback) {
|
|
|
3399
3475
|
function useRecoilSnapshot() {
|
|
3400
3476
|
var _a;
|
|
3401
3477
|
const storeRef = useStoreRef();
|
|
3402
|
-
const [snapshot, setSnapshot] = React.useState(() =>
|
|
3478
|
+
const [snapshot, setSnapshot] = React.useState(() => {
|
|
3479
|
+
try {
|
|
3480
|
+
return cloneSnapshot(storeRef.current);
|
|
3481
|
+
}
|
|
3482
|
+
catch (error) {
|
|
3483
|
+
// In React 19, snapshots can be released more aggressively
|
|
3484
|
+
// If the snapshot was already released, create a fresh one
|
|
3485
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3486
|
+
typeof error.message === 'string' &&
|
|
3487
|
+
error.message.includes('already been released')) {
|
|
3488
|
+
console.warn('Snapshot already released during initial state, creating fresh snapshot');
|
|
3489
|
+
return cloneSnapshot(storeRef.current);
|
|
3490
|
+
}
|
|
3491
|
+
throw error;
|
|
3492
|
+
}
|
|
3493
|
+
});
|
|
3403
3494
|
const previousSnapshot = usePrevious(snapshot);
|
|
3404
3495
|
const timeoutID = React.useRef(null);
|
|
3405
3496
|
const releaseRef = React.useRef(null);
|
|
3406
|
-
useTransactionSubscription(React.useCallback((store) =>
|
|
3497
|
+
useTransactionSubscription(React.useCallback((store) => {
|
|
3498
|
+
try {
|
|
3499
|
+
setSnapshot(cloneSnapshot(store));
|
|
3500
|
+
}
|
|
3501
|
+
catch (error) {
|
|
3502
|
+
// In React 19, snapshots can be released more aggressively
|
|
3503
|
+
// If the snapshot was already released, skip this update
|
|
3504
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3505
|
+
typeof error.message === 'string' &&
|
|
3506
|
+
error.message.includes('already been released')) {
|
|
3507
|
+
console.warn('Snapshot already released during transaction subscription, skipping update');
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
throw error;
|
|
3511
|
+
}
|
|
3512
|
+
}, []));
|
|
3407
3513
|
// Retain snapshot for duration component is mounted
|
|
3408
3514
|
React.useEffect(() => {
|
|
3409
3515
|
var _a;
|
|
3410
|
-
|
|
3516
|
+
let release = null;
|
|
3517
|
+
try {
|
|
3518
|
+
release = snapshot.retain();
|
|
3519
|
+
}
|
|
3520
|
+
catch (error) {
|
|
3521
|
+
// If snapshot retention fails, skip this effect
|
|
3522
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3523
|
+
typeof error.message === 'string' &&
|
|
3524
|
+
error.message.includes('already been released')) {
|
|
3525
|
+
console.warn('Cannot retain snapshot in useEffect, already released');
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
throw error;
|
|
3529
|
+
}
|
|
3411
3530
|
// Release the retain from the rendering call
|
|
3412
3531
|
if (timeoutID.current && !isSSR) {
|
|
3413
3532
|
window.clearTimeout(timeoutID.current);
|
|
@@ -3421,7 +3540,9 @@ function useRecoilSnapshot() {
|
|
|
3421
3540
|
// then the new effect will run. We don't want the snapshot to be released
|
|
3422
3541
|
// by that cleanup before the new effect has a chance to retain it again.
|
|
3423
3542
|
// Use timeout of 10 to workaround Firefox issue: https://github.com/facebookexperimental/Recoil/issues/1936
|
|
3424
|
-
|
|
3543
|
+
if (release) {
|
|
3544
|
+
window.setTimeout(release, 10);
|
|
3545
|
+
}
|
|
3425
3546
|
};
|
|
3426
3547
|
}, [snapshot]);
|
|
3427
3548
|
// Retain snapshot until above effect is run.
|
|
@@ -3434,7 +3555,21 @@ function useRecoilSnapshot() {
|
|
|
3434
3555
|
(_a = releaseRef.current) === null || _a === void 0 ? void 0 : _a.call(releaseRef);
|
|
3435
3556
|
releaseRef.current = null;
|
|
3436
3557
|
}
|
|
3437
|
-
|
|
3558
|
+
try {
|
|
3559
|
+
releaseRef.current = snapshot.retain();
|
|
3560
|
+
}
|
|
3561
|
+
catch (error) {
|
|
3562
|
+
// If snapshot retention fails, skip this retention
|
|
3563
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3564
|
+
typeof error.message === 'string' &&
|
|
3565
|
+
error.message.includes('already been released')) {
|
|
3566
|
+
console.warn('Cannot retain snapshot in render, already released');
|
|
3567
|
+
releaseRef.current = null;
|
|
3568
|
+
}
|
|
3569
|
+
else {
|
|
3570
|
+
throw error;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3438
3573
|
timeoutID.current = window.setTimeout(() => {
|
|
3439
3574
|
var _a;
|
|
3440
3575
|
timeoutID.current = null;
|
|
@@ -3444,33 +3579,62 @@ function useRecoilSnapshot() {
|
|
|
3444
3579
|
}
|
|
3445
3580
|
return snapshot;
|
|
3446
3581
|
}
|
|
3582
|
+
function notifyComponents(store, treeState) {
|
|
3583
|
+
const storeState = store.getState();
|
|
3584
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
3585
|
+
for (const key of dependentNodes) {
|
|
3586
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
3587
|
+
if (comps) {
|
|
3588
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
3589
|
+
try {
|
|
3590
|
+
callback(treeState);
|
|
3591
|
+
}
|
|
3592
|
+
catch (error) {
|
|
3593
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3447
3599
|
function gotoSnapshot(store, snapshot) {
|
|
3448
3600
|
var _a;
|
|
3449
3601
|
const storeState = store.getState();
|
|
3450
3602
|
const prev = (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree;
|
|
3451
3603
|
const next = snapshot.getStore_INTERNAL().getState().currentTree;
|
|
3452
3604
|
batchUpdates(() => {
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3605
|
+
store.replaceState(currentTree => {
|
|
3606
|
+
var _a, _b;
|
|
3607
|
+
const newTree = copyTreeState(currentTree);
|
|
3608
|
+
newTree.stateID = snapshot.getID();
|
|
3609
|
+
const atomKeysChanged = new Set();
|
|
3610
|
+
// Update atoms that should be restored from snapshots
|
|
3611
|
+
for (const key of new Set([...prev.atomValues.keys(), ...next.atomValues.keys()])) {
|
|
3612
|
+
const node = getNode(key);
|
|
3613
|
+
if (!node.shouldRestoreFromSnapshots)
|
|
3614
|
+
continue;
|
|
3615
|
+
const prevContents = (_a = prev.atomValues.get(key)) === null || _a === void 0 ? void 0 : _a.contents;
|
|
3616
|
+
const nextContents = (_b = next.atomValues.get(key)) === null || _b === void 0 ? void 0 : _b.contents;
|
|
3617
|
+
if (prevContents !== nextContents) {
|
|
3618
|
+
atomKeysChanged.add(key);
|
|
3619
|
+
const loadable = next.atomValues.has(key)
|
|
3620
|
+
? nullthrows(next.atomValues.get(key))
|
|
3621
|
+
: loadableWithValue(DEFAULT_VALUE);
|
|
3622
|
+
if (loadable && loadable.state === 'hasValue' && loadable.contents === DEFAULT_VALUE) {
|
|
3623
|
+
newTree.atomValues.delete(key);
|
|
3624
|
+
}
|
|
3625
|
+
else {
|
|
3626
|
+
newTree.atomValues.set(key, loadable);
|
|
3627
|
+
}
|
|
3628
|
+
newTree.dirtyAtoms.add(key);
|
|
3461
3629
|
}
|
|
3462
3630
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadable);
|
|
3468
|
-
}
|
|
3469
|
-
else {
|
|
3470
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadableWithValue(DEFAULT_VALUE));
|
|
3631
|
+
// If atoms changed, invalidate dependent selectors and notify components
|
|
3632
|
+
if (atomKeysChanged.size > 0) {
|
|
3633
|
+
invalidateDownstreams(store, newTree);
|
|
3634
|
+
notifyComponents(store, newTree);
|
|
3471
3635
|
}
|
|
3636
|
+
return newTree;
|
|
3472
3637
|
});
|
|
3473
|
-
store.replaceState(state => (Object.assign(Object.assign({}, state), { stateID: snapshot.getID() })));
|
|
3474
3638
|
});
|
|
3475
3639
|
}
|
|
3476
3640
|
function useGotoRecoilSnapshot() {
|
|
@@ -3590,11 +3754,40 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3590
3754
|
if (typeof fn !== 'function') {
|
|
3591
3755
|
throw err(errMsg);
|
|
3592
3756
|
}
|
|
3593
|
-
|
|
3757
|
+
// Create snapshots for different read types
|
|
3758
|
+
let originalSnapshot;
|
|
3759
|
+
let currentSnapshot;
|
|
3760
|
+
const baseInterface = Object.assign(Object.assign({}, (extraInterface !== null && extraInterface !== void 0 ? extraInterface : {})), { set: (node, newValue) => {
|
|
3761
|
+
setRecoilValue(store, node, newValue);
|
|
3762
|
+
}, reset: (node) => {
|
|
3763
|
+
setRecoilValue(store, node, DEFAULT_VALUE);
|
|
3764
|
+
}, refresh: (node) => refreshRecoilValue(store, node), gotoSnapshot: (snapshot) => gotoSnapshot(store, snapshot), transact_UNSTABLE: (transaction) => atomicUpdater(store)(transaction) });
|
|
3765
|
+
const callbackInterface = lazyProxy(baseInterface, {
|
|
3594
3766
|
snapshot: () => {
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3767
|
+
if (!originalSnapshot) {
|
|
3768
|
+
originalSnapshot = cloneSnapshot(store, 'latest');
|
|
3769
|
+
releaseSnapshot = originalSnapshot.retain();
|
|
3770
|
+
}
|
|
3771
|
+
// Create a hybrid snapshot that handles both behaviors
|
|
3772
|
+
const hybridSnapshot = new Proxy(originalSnapshot, {
|
|
3773
|
+
get(target, prop) {
|
|
3774
|
+
if (prop === 'getLoadable') {
|
|
3775
|
+
// For getLoadable, return current store state (reflects changes)
|
|
3776
|
+
return (recoilValue) => {
|
|
3777
|
+
currentSnapshot = cloneSnapshot(store, 'latest');
|
|
3778
|
+
return currentSnapshot.getLoadable(recoilValue);
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
else if (prop === 'getPromise') {
|
|
3782
|
+
// For getPromise, return original state (doesn't reflect changes)
|
|
3783
|
+
return target.getPromise.bind(target);
|
|
3784
|
+
}
|
|
3785
|
+
// For all other methods, delegate to target
|
|
3786
|
+
const value = target[prop];
|
|
3787
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
3788
|
+
}
|
|
3789
|
+
});
|
|
3790
|
+
return hybridSnapshot;
|
|
3598
3791
|
},
|
|
3599
3792
|
});
|
|
3600
3793
|
const callback = fn(callbackInterface);
|
|
@@ -3616,11 +3809,21 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3616
3809
|
}
|
|
3617
3810
|
function useRecoilCallback(fn, deps) {
|
|
3618
3811
|
const storeRef = useStoreRef();
|
|
3812
|
+
const isRenderingRef = React.useRef(true);
|
|
3813
|
+
// Clear the render flag after render completes
|
|
3814
|
+
React.useLayoutEffect(() => {
|
|
3815
|
+
isRenderingRef.current = false;
|
|
3816
|
+
});
|
|
3619
3817
|
return React.useCallback((...args) => {
|
|
3818
|
+
if (isRenderingRef.current) {
|
|
3819
|
+
throw err('useRecoilCallback() hooks cannot be called during render. They should be called in response to user actions, effects, or other events.');
|
|
3820
|
+
}
|
|
3620
3821
|
return recoilCallback(storeRef.current, fn, args);
|
|
3621
3822
|
},
|
|
3823
|
+
// Don't include storeRef in deps to avoid unnecessary re-creation
|
|
3824
|
+
// The store reference should be stable within a RecoilRoot
|
|
3622
3825
|
// eslint-disable-next-line fb-www/react-hooks-deps
|
|
3623
|
-
deps
|
|
3826
|
+
deps !== null && deps !== void 0 ? deps : []);
|
|
3624
3827
|
}
|
|
3625
3828
|
|
|
3626
3829
|
/**
|
|
@@ -3724,6 +3927,116 @@ function deepFreezeValue(value) {
|
|
|
3724
3927
|
Object.seal(value);
|
|
3725
3928
|
}
|
|
3726
3929
|
|
|
3930
|
+
/**
|
|
3931
|
+
* TypeScript port of Recoil_stableStringify.js
|
|
3932
|
+
*/
|
|
3933
|
+
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
3934
|
+
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
3935
|
+
function stringify(x, opt, key, visited = new Set()) {
|
|
3936
|
+
var _a;
|
|
3937
|
+
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
3938
|
+
return `"${x}"`;
|
|
3939
|
+
}
|
|
3940
|
+
switch (typeof x) {
|
|
3941
|
+
case 'undefined':
|
|
3942
|
+
return '';
|
|
3943
|
+
case 'boolean':
|
|
3944
|
+
return x ? 'true' : 'false';
|
|
3945
|
+
case 'number':
|
|
3946
|
+
case 'symbol':
|
|
3947
|
+
return String(x);
|
|
3948
|
+
case 'string':
|
|
3949
|
+
return JSON.stringify(x);
|
|
3950
|
+
case 'function':
|
|
3951
|
+
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
3952
|
+
return '';
|
|
3953
|
+
}
|
|
3954
|
+
return `__FUNCTION(${x.name})__`;
|
|
3955
|
+
}
|
|
3956
|
+
if (x === null) {
|
|
3957
|
+
return 'null';
|
|
3958
|
+
}
|
|
3959
|
+
if (typeof x !== 'object') {
|
|
3960
|
+
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
3961
|
+
}
|
|
3962
|
+
// Handle circular references
|
|
3963
|
+
if (visited.has(x)) {
|
|
3964
|
+
return '__CIRCULAR__';
|
|
3965
|
+
}
|
|
3966
|
+
visited.add(x);
|
|
3967
|
+
if (isPromise(x)) {
|
|
3968
|
+
visited.delete(x);
|
|
3969
|
+
return '__PROMISE__';
|
|
3970
|
+
}
|
|
3971
|
+
if (Array.isArray(x)) {
|
|
3972
|
+
const result = `[${x.map((v, i) => stringify(v, opt, i.toString(), visited)).join(',')}]`;
|
|
3973
|
+
visited.delete(x);
|
|
3974
|
+
return result;
|
|
3975
|
+
}
|
|
3976
|
+
if (typeof x.toJSON === 'function') {
|
|
3977
|
+
const result = stringify(x.toJSON(key), opt, key, visited);
|
|
3978
|
+
visited.delete(x);
|
|
3979
|
+
return result;
|
|
3980
|
+
}
|
|
3981
|
+
if (x instanceof Map) {
|
|
3982
|
+
const obj = {};
|
|
3983
|
+
for (const [k, v] of x) {
|
|
3984
|
+
obj[typeof k === 'string' ? k : stringify(k, opt, undefined, visited)] = v;
|
|
3985
|
+
}
|
|
3986
|
+
const result = stringify(obj, opt, key, visited);
|
|
3987
|
+
visited.delete(x);
|
|
3988
|
+
return result;
|
|
3989
|
+
}
|
|
3990
|
+
if (x instanceof Set) {
|
|
3991
|
+
const sortedItems = Array.from(x).sort((a, b) => {
|
|
3992
|
+
const aStr = stringify(a, opt, undefined, new Set(visited));
|
|
3993
|
+
const bStr = stringify(b, opt, undefined, new Set(visited));
|
|
3994
|
+
// Use a more predictable sort order - null should come first
|
|
3995
|
+
if (aStr === 'null' && bStr !== 'null')
|
|
3996
|
+
return -1;
|
|
3997
|
+
if (bStr === 'null' && aStr !== 'null')
|
|
3998
|
+
return 1;
|
|
3999
|
+
return aStr.localeCompare(bStr);
|
|
4000
|
+
});
|
|
4001
|
+
const result = stringify(sortedItems, opt, key, visited);
|
|
4002
|
+
visited.delete(x);
|
|
4003
|
+
return result;
|
|
4004
|
+
}
|
|
4005
|
+
if (Symbol !== undefined &&
|
|
4006
|
+
x[Symbol.iterator] != null &&
|
|
4007
|
+
typeof x[Symbol.iterator] === 'function') {
|
|
4008
|
+
const result = stringify(Array.from(x), opt, key, visited);
|
|
4009
|
+
visited.delete(x);
|
|
4010
|
+
return result;
|
|
4011
|
+
}
|
|
4012
|
+
const result = `{${Object.keys(x)
|
|
4013
|
+
.filter(k => {
|
|
4014
|
+
const value = x[k];
|
|
4015
|
+
return value !== undefined && typeof value !== 'function';
|
|
4016
|
+
})
|
|
4017
|
+
.sort()
|
|
4018
|
+
.map(k => `${stringify(k, opt, undefined, visited)}:${stringify(x[k], opt, k, visited)}`)
|
|
4019
|
+
.join(',')}}`;
|
|
4020
|
+
visited.delete(x);
|
|
4021
|
+
return result;
|
|
4022
|
+
}
|
|
4023
|
+
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4024
|
+
if (__DEV__$2) {
|
|
4025
|
+
if (typeof window !== 'undefined') {
|
|
4026
|
+
const startTime = window.performance ? window.performance.now() : 0;
|
|
4027
|
+
const str = stringify(x, opt);
|
|
4028
|
+
const endTime = window.performance ? window.performance.now() : 0;
|
|
4029
|
+
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4030
|
+
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4031
|
+
console.warn(x, str);
|
|
4032
|
+
console.groupEnd();
|
|
4033
|
+
}
|
|
4034
|
+
return str;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
return stringify(x, opt);
|
|
4038
|
+
}
|
|
4039
|
+
|
|
3727
4040
|
/**
|
|
3728
4041
|
* TypeScript port of Recoil_TreeCache.js
|
|
3729
4042
|
*/
|
|
@@ -3767,60 +4080,63 @@ class TreeCache {
|
|
|
3767
4080
|
}
|
|
3768
4081
|
set(route, value, handlers) {
|
|
3769
4082
|
const addLeaf = () => {
|
|
3770
|
-
var _a, _b, _c, _d;
|
|
3771
|
-
|
|
3772
|
-
let
|
|
4083
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4084
|
+
// First, setup the branch nodes for the route:
|
|
4085
|
+
let node = null;
|
|
4086
|
+
let branchKey = undefined;
|
|
3773
4087
|
for (const [nodeKey, nodeValue] of route) {
|
|
4088
|
+
// node now refers to the next node down in the tree
|
|
4089
|
+
const parent = node;
|
|
4090
|
+
// Get existing node or create a new one
|
|
3774
4091
|
const root = this._root;
|
|
3775
|
-
|
|
4092
|
+
const existing = parent ? parent.branches.get(branchKey) : root;
|
|
4093
|
+
node = (_a = existing) !== null && _a !== void 0 ? _a : {
|
|
4094
|
+
type: 'branch',
|
|
4095
|
+
nodeKey,
|
|
4096
|
+
parent,
|
|
4097
|
+
branches: new Map(),
|
|
4098
|
+
branchKey,
|
|
4099
|
+
};
|
|
4100
|
+
// If we found an existing node, confirm it has a consistent value
|
|
4101
|
+
if (node.type !== 'branch' || node.nodeKey !== nodeKey) {
|
|
3776
4102
|
throw this.invalidCacheError();
|
|
3777
4103
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
current = {
|
|
3782
|
-
type: 'branch',
|
|
3783
|
-
nodeKey,
|
|
3784
|
-
parent,
|
|
3785
|
-
branches: new Map(),
|
|
3786
|
-
branchKey,
|
|
3787
|
-
};
|
|
3788
|
-
if (parent) {
|
|
3789
|
-
parent.branches.set(branchKey, current);
|
|
3790
|
-
}
|
|
3791
|
-
else {
|
|
3792
|
-
this._root = current;
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
if (current.type !== 'branch' || current.nodeKey !== nodeKey) {
|
|
3796
|
-
throw this.invalidCacheError();
|
|
4104
|
+
// Add the branch node to the tree
|
|
4105
|
+
if (parent) {
|
|
4106
|
+
parent.branches.set(branchKey, node);
|
|
3797
4107
|
}
|
|
3798
|
-
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers,
|
|
3799
|
-
|
|
4108
|
+
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers, node);
|
|
4109
|
+
// Prepare for next iteration and install root if it is new.
|
|
3800
4110
|
branchKey = this._mapNodeValue(nodeValue);
|
|
4111
|
+
this._root = (_c = this._root) !== null && _c !== void 0 ? _c : node;
|
|
3801
4112
|
}
|
|
4113
|
+
// Second, setup the leaf node:
|
|
4114
|
+
// If there is an existing leaf for this route confirm it is consistent
|
|
3802
4115
|
const oldLeaf = node
|
|
3803
|
-
? (
|
|
4116
|
+
? (_d = node.branches.get(branchKey)) !== null && _d !== void 0 ? _d : null
|
|
3804
4117
|
: this._root;
|
|
3805
4118
|
if (oldLeaf != null &&
|
|
3806
4119
|
(oldLeaf.type !== 'leaf' || oldLeaf.branchKey !== branchKey)) {
|
|
3807
4120
|
throw this.invalidCacheError();
|
|
3808
4121
|
}
|
|
4122
|
+
// Create a new or replacement leaf.
|
|
3809
4123
|
const leafNode = {
|
|
3810
4124
|
type: 'leaf',
|
|
3811
4125
|
value,
|
|
3812
4126
|
parent: node,
|
|
3813
4127
|
branchKey,
|
|
3814
4128
|
};
|
|
4129
|
+
// Install the leaf and call handlers
|
|
3815
4130
|
if (node) {
|
|
3816
4131
|
node.branches.set(branchKey, leafNode);
|
|
3817
4132
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
4133
|
+
this._root = (_e = this._root) !== null && _e !== void 0 ? _e : leafNode;
|
|
4134
|
+
// Only increment if this is a new leaf (not a replacement)
|
|
4135
|
+
if (oldLeaf == null) {
|
|
4136
|
+
this._numLeafs++;
|
|
3820
4137
|
}
|
|
3821
|
-
this._numLeafs++;
|
|
3822
4138
|
this._onSet(leafNode);
|
|
3823
|
-
(
|
|
4139
|
+
(_f = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _f === void 0 ? void 0 : _f.call(handlers, leafNode);
|
|
3824
4140
|
};
|
|
3825
4141
|
try {
|
|
3826
4142
|
addLeaf();
|
|
@@ -4014,85 +4330,6 @@ function treeCacheLRU({ name, maxSize, mapNodeValue = (v) => v, }) {
|
|
|
4014
4330
|
return cache;
|
|
4015
4331
|
}
|
|
4016
4332
|
|
|
4017
|
-
/**
|
|
4018
|
-
* TypeScript port of Recoil_stableStringify.js
|
|
4019
|
-
*/
|
|
4020
|
-
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
4021
|
-
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
4022
|
-
function stringify(x, opt, key) {
|
|
4023
|
-
var _a;
|
|
4024
|
-
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
4025
|
-
return `"${x}"`;
|
|
4026
|
-
}
|
|
4027
|
-
switch (typeof x) {
|
|
4028
|
-
case 'undefined':
|
|
4029
|
-
return '';
|
|
4030
|
-
case 'boolean':
|
|
4031
|
-
return x ? 'true' : 'false';
|
|
4032
|
-
case 'number':
|
|
4033
|
-
case 'symbol':
|
|
4034
|
-
return String(x);
|
|
4035
|
-
case 'string':
|
|
4036
|
-
return JSON.stringify(x);
|
|
4037
|
-
case 'function':
|
|
4038
|
-
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
4039
|
-
throw err('Attempt to serialize function in a Recoil cache key');
|
|
4040
|
-
}
|
|
4041
|
-
return `__FUNCTION(${x.name})__`;
|
|
4042
|
-
}
|
|
4043
|
-
if (x === null) {
|
|
4044
|
-
return 'null';
|
|
4045
|
-
}
|
|
4046
|
-
if (typeof x !== 'object') {
|
|
4047
|
-
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
4048
|
-
}
|
|
4049
|
-
if (isPromise(x)) {
|
|
4050
|
-
return '__PROMISE__';
|
|
4051
|
-
}
|
|
4052
|
-
if (Array.isArray(x)) {
|
|
4053
|
-
return `[${x.map((v, i) => stringify(v, opt, i.toString()))}]`;
|
|
4054
|
-
}
|
|
4055
|
-
if (typeof x.toJSON === 'function') {
|
|
4056
|
-
return stringify(x.toJSON(key), opt, key);
|
|
4057
|
-
}
|
|
4058
|
-
if (x instanceof Map) {
|
|
4059
|
-
const obj = {};
|
|
4060
|
-
for (const [k, v] of x) {
|
|
4061
|
-
obj[typeof k === 'string' ? k : stringify(k, opt)] = v;
|
|
4062
|
-
}
|
|
4063
|
-
return stringify(obj, opt, key);
|
|
4064
|
-
}
|
|
4065
|
-
if (x instanceof Set) {
|
|
4066
|
-
return stringify(Array.from(x).sort((a, b) => stringify(a, opt).localeCompare(stringify(b, opt))), opt, key);
|
|
4067
|
-
}
|
|
4068
|
-
if (Symbol !== undefined &&
|
|
4069
|
-
x[Symbol.iterator] != null &&
|
|
4070
|
-
typeof x[Symbol.iterator] === 'function') {
|
|
4071
|
-
return stringify(Array.from(x), opt, key);
|
|
4072
|
-
}
|
|
4073
|
-
return `{${Object.keys(x)
|
|
4074
|
-
.filter(k => x[k] !== undefined)
|
|
4075
|
-
.sort()
|
|
4076
|
-
.map(k => `${stringify(k, opt)}:${stringify(x[k], opt, k)}`)
|
|
4077
|
-
.join(',')}}`;
|
|
4078
|
-
}
|
|
4079
|
-
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4080
|
-
if (__DEV__$2) {
|
|
4081
|
-
if (typeof window !== 'undefined') {
|
|
4082
|
-
const startTime = window.performance ? window.performance.now() : 0;
|
|
4083
|
-
const str = stringify(x, opt);
|
|
4084
|
-
const endTime = window.performance ? window.performance.now() : 0;
|
|
4085
|
-
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4086
|
-
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4087
|
-
console.warn(x, str);
|
|
4088
|
-
console.groupEnd();
|
|
4089
|
-
}
|
|
4090
|
-
return str;
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
return stringify(x, opt);
|
|
4094
|
-
}
|
|
4095
|
-
|
|
4096
4333
|
/**
|
|
4097
4334
|
* TypeScript port of Recoil_treeCacheFromPolicy.js
|
|
4098
4335
|
*/
|
|
@@ -4115,7 +4352,6 @@ function getValueMapper$1(equality) {
|
|
|
4115
4352
|
case 'value':
|
|
4116
4353
|
return val => stableStringify(val);
|
|
4117
4354
|
}
|
|
4118
|
-
throw err(`Unrecognized equality policy ${equality}`);
|
|
4119
4355
|
}
|
|
4120
4356
|
function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
4121
4357
|
switch (eviction) {
|
|
@@ -4130,7 +4366,6 @@ function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
|
4130
4366
|
case 'most-recent':
|
|
4131
4367
|
return treeCacheLRU({ name, maxSize: 1, mapNodeValue });
|
|
4132
4368
|
}
|
|
4133
|
-
throw err(`Unrecognized eviction policy ${eviction}`);
|
|
4134
4369
|
}
|
|
4135
4370
|
|
|
4136
4371
|
/**
|
|
@@ -4297,10 +4532,9 @@ function selector(options) {
|
|
|
4297
4532
|
}
|
|
4298
4533
|
function updateDeps(store, state, deps, executionID) {
|
|
4299
4534
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
4300
|
-
if (executionID != null &&
|
|
4301
|
-
(
|
|
4302
|
-
|
|
4303
|
-
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version))) {
|
|
4535
|
+
if ((executionID != null && isLatestExecution(store, executionID)) ||
|
|
4536
|
+
state.version === ((_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.currentTree) === null || _b === void 0 ? void 0 : _b.version) ||
|
|
4537
|
+
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version)) {
|
|
4304
4538
|
saveDepsToStore(key, deps, store, (_g = (_f = (_e = store.getState()) === null || _e === void 0 ? void 0 : _e.nextTree) === null || _f === void 0 ? void 0 : _f.version) !== null && _g !== void 0 ? _g : store.getState().currentTree.version);
|
|
4305
4539
|
}
|
|
4306
4540
|
for (const nodeKey of deps) {
|
|
@@ -4565,7 +4799,8 @@ function selector(options) {
|
|
|
4565
4799
|
discoveredDependencyNodeKeys.clear();
|
|
4566
4800
|
invalidateSelector(treeState);
|
|
4567
4801
|
cache.clear();
|
|
4568
|
-
markRecoilValueModified
|
|
4802
|
+
// Don't call markRecoilValueModified here as it causes nested state updates
|
|
4803
|
+
// The caller (like refreshRecoilValue) should handle marking as dirty
|
|
4569
4804
|
}
|
|
4570
4805
|
if (set != null) {
|
|
4571
4806
|
const selectorSet = (store, state, newValue) => {
|
|
@@ -5056,12 +5291,7 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5056
5291
|
}
|
|
5057
5292
|
|
|
5058
5293
|
/**
|
|
5059
|
-
*
|
|
5060
|
-
*
|
|
5061
|
-
* This source code is licensed under the MIT license found in the
|
|
5062
|
-
* LICENSE file in the root directory of this source tree.
|
|
5063
|
-
*
|
|
5064
|
-
* @oncall recoil
|
|
5294
|
+
* TypeScript port of Recoil_atomFamily.js
|
|
5065
5295
|
*/
|
|
5066
5296
|
function atomFamily(options) {
|
|
5067
5297
|
var _a, _b;
|
|
@@ -5171,12 +5401,7 @@ function constSelector(constant) {
|
|
|
5171
5401
|
}
|
|
5172
5402
|
|
|
5173
5403
|
/**
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
5176
|
-
* This source code is licensed under the MIT license found in the
|
|
5177
|
-
* LICENSE file in the root directory of this source tree.
|
|
5178
|
-
*
|
|
5179
|
-
* @oncall recoil
|
|
5404
|
+
* TypeScript port of Recoil_errorSelector.js
|
|
5180
5405
|
*/
|
|
5181
5406
|
const throwingSelector = selectorFamily({
|
|
5182
5407
|
key: '__error',
|
|
@@ -5192,14 +5417,7 @@ function errorSelector(message) {
|
|
|
5192
5417
|
}
|
|
5193
5418
|
|
|
5194
5419
|
/**
|
|
5195
|
-
*
|
|
5196
|
-
*
|
|
5197
|
-
* This source code is licensed under the MIT license found in the
|
|
5198
|
-
* LICENSE file in the root directory of this source tree.
|
|
5199
|
-
*
|
|
5200
|
-
* Wraps another recoil value and prevents writing to it.
|
|
5201
|
-
*
|
|
5202
|
-
* @oncall recoil
|
|
5420
|
+
* TypeScript port of Recoil_readOnlySelector.js
|
|
5203
5421
|
*/
|
|
5204
5422
|
function readOnlySelector(atom) {
|
|
5205
5423
|
return atom;
|
|
@@ -5260,11 +5478,21 @@ const waitForAny = selectorFamily({
|
|
|
5260
5478
|
const deps = unwrapDependencies(dependencies);
|
|
5261
5479
|
const [results, exceptions] = concurrentRequests(get, deps);
|
|
5262
5480
|
if (exceptions.some(exp => !isPromise(exp))) {
|
|
5481
|
+
// If all are errors (no promises), waitForAny should throw the first error
|
|
5482
|
+
if (exceptions.every(exp => isError(exp))) {
|
|
5483
|
+
const firstError = exceptions.find(isError);
|
|
5484
|
+
if (firstError) {
|
|
5485
|
+
throw firstError;
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5263
5488
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5264
5489
|
}
|
|
5265
|
-
return new Promise(resolve => {
|
|
5490
|
+
return new Promise((resolve, reject) => {
|
|
5491
|
+
let pendingCount = 0;
|
|
5492
|
+
let settledCount = 0;
|
|
5266
5493
|
for (const [i, exp] of exceptions.entries()) {
|
|
5267
5494
|
if (isPromise(exp)) {
|
|
5495
|
+
pendingCount++;
|
|
5268
5496
|
exp
|
|
5269
5497
|
.then(result => {
|
|
5270
5498
|
results[i] = result;
|
|
@@ -5273,7 +5501,18 @@ const waitForAny = selectorFamily({
|
|
|
5273
5501
|
})
|
|
5274
5502
|
.catch(error => {
|
|
5275
5503
|
exceptions[i] = error;
|
|
5276
|
-
|
|
5504
|
+
settledCount++;
|
|
5505
|
+
// Only resolve with error if ALL promises have settled/failed
|
|
5506
|
+
if (settledCount === pendingCount) {
|
|
5507
|
+
// All promises have settled with errors, so reject with the first error
|
|
5508
|
+
const firstError = exceptions.find(isError);
|
|
5509
|
+
if (firstError) {
|
|
5510
|
+
reject(firstError);
|
|
5511
|
+
}
|
|
5512
|
+
else {
|
|
5513
|
+
resolve(wrapLoadables(dependencies, results, exceptions));
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5277
5516
|
});
|
|
5278
5517
|
}
|
|
5279
5518
|
}
|