recoil-next 0.2.0 → 0.4.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/README.md +5 -5
- package/dist/index.cjs +486 -217
- package/dist/index.d.cts +9 -26
- package/dist/index.d.ts +9 -26
- package/dist/index.mjs +487 -218
- package/package.json +12 -12
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React, { useRef, createContext, useContext, useCallback, useEffect, Suspense, useState, useMemo, useSyncExternalStore as useSyncExternalStore$1 } from 'react';
|
|
2
|
+
import React, { useRef, createContext, useContext, useCallback, useEffect, Suspense, useState, useMemo, useSyncExternalStore as useSyncExternalStore$1, useLayoutEffect } from 'react';
|
|
3
3
|
import { unstable_batchedUpdates } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -11,7 +11,9 @@ function err(message) {
|
|
|
11
11
|
try {
|
|
12
12
|
throw error;
|
|
13
13
|
}
|
|
14
|
-
catch (_) {
|
|
14
|
+
catch (_) {
|
|
15
|
+
/* T-ignore */
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
return error;
|
|
17
19
|
}
|
|
@@ -112,7 +114,8 @@ class BaseLoadable {
|
|
|
112
114
|
throw err(`Loadable expected error, but in "${this.state}" state`);
|
|
113
115
|
}
|
|
114
116
|
is(other) {
|
|
115
|
-
return other.state === this.state &&
|
|
117
|
+
return (other.state === this.state &&
|
|
118
|
+
Object.is(other.contents, this.contents));
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
class ValueLoadable extends BaseLoadable {
|
|
@@ -822,7 +825,6 @@ const getNextComponentID = () => nextComponentID++;
|
|
|
822
825
|
/** React mode and feature detection helpers */
|
|
823
826
|
var _a;
|
|
824
827
|
// Access either useSyncExternalStore or unstable_useSyncExternalStore depending on React version.
|
|
825
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
826
828
|
const useSyncExternalStore =
|
|
827
829
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
828
830
|
(_a = React.useSyncExternalStore) !== null && _a !== void 0 ? _a :
|
|
@@ -833,14 +835,17 @@ let ReactRendererVersionMismatchWarnOnce = false;
|
|
|
833
835
|
function currentRendererSupportsUseSyncExternalStore() {
|
|
834
836
|
var _a;
|
|
835
837
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
836
|
-
const internals = React
|
|
838
|
+
const internals = React
|
|
839
|
+
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
837
840
|
if (!internals)
|
|
838
841
|
return false;
|
|
839
842
|
const { ReactCurrentDispatcher, ReactCurrentOwner } = internals;
|
|
840
843
|
// The dispatcher can be on ReactCurrentDispatcher.current (newer) or ReactCurrentOwner.currentDispatcher (older)
|
|
841
844
|
const dispatcher = (_a = ReactCurrentDispatcher === null || ReactCurrentDispatcher === void 0 ? void 0 : ReactCurrentDispatcher.current) !== null && _a !== void 0 ? _a : ReactCurrentOwner === null || ReactCurrentOwner === void 0 ? void 0 : ReactCurrentOwner.currentDispatcher;
|
|
842
845
|
const isSupported = (dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.useSyncExternalStore) != null;
|
|
843
|
-
if (useSyncExternalStore !== undefined &&
|
|
846
|
+
if (useSyncExternalStore !== undefined &&
|
|
847
|
+
!isSupported &&
|
|
848
|
+
!ReactRendererVersionMismatchWarnOnce) {
|
|
844
849
|
ReactRendererVersionMismatchWarnOnce = true;
|
|
845
850
|
recoverableViolation('A React renderer without React 18+ API support is being used with React 18+.');
|
|
846
851
|
}
|
|
@@ -891,7 +896,8 @@ writes) {
|
|
|
891
896
|
return result;
|
|
892
897
|
}
|
|
893
898
|
function writeLoadableToTreeState(state, key, loadable) {
|
|
894
|
-
if (loadable.state === 'hasValue' &&
|
|
899
|
+
if (loadable.state === 'hasValue' &&
|
|
900
|
+
loadable.contents instanceof DefaultValue) {
|
|
895
901
|
state.atomValues.delete(key);
|
|
896
902
|
}
|
|
897
903
|
else {
|
|
@@ -905,11 +911,30 @@ function markRecoilValueModified(store, rv) {
|
|
|
905
911
|
store.replaceState(state => {
|
|
906
912
|
const newState = copyTreeState(state);
|
|
907
913
|
newState.dirtyAtoms.add(rv.key);
|
|
914
|
+
notifyComponents$2(store, newState);
|
|
908
915
|
return newState;
|
|
909
916
|
});
|
|
910
917
|
}
|
|
918
|
+
function notifyComponents$2(store, treeState) {
|
|
919
|
+
const storeState = store.getState();
|
|
920
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
921
|
+
for (const key of dependentNodes) {
|
|
922
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
923
|
+
if (comps) {
|
|
924
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
925
|
+
try {
|
|
926
|
+
callback(treeState);
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
911
935
|
function valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater) {
|
|
912
|
-
if (typeof valueOrUpdater === 'function' &&
|
|
936
|
+
if (typeof valueOrUpdater === 'function' &&
|
|
937
|
+
valueOrUpdater !== DEFAULT_VALUE) {
|
|
913
938
|
// Updater form: pass in the current value
|
|
914
939
|
const current = getRecoilValueAsLoadable(store, recoilValue, state);
|
|
915
940
|
if (current.state === 'loading') {
|
|
@@ -933,6 +958,7 @@ function setRecoilValue(store, recoilValue, valueOrUpdater) {
|
|
|
933
958
|
const writes = setNodeValue(store, newState, recoilValue.key, newValue);
|
|
934
959
|
writes.forEach((loadable, key) => writeLoadableToTreeState(newState, key, loadable));
|
|
935
960
|
invalidateDownstreams(store, newState);
|
|
961
|
+
newState.dirtyAtoms.add(recoilValue.key);
|
|
936
962
|
return newState;
|
|
937
963
|
});
|
|
938
964
|
}
|
|
@@ -967,10 +993,18 @@ function subscribeToRecoilValue(store, { key }, callback, _componentDebugName) {
|
|
|
967
993
|
};
|
|
968
994
|
}
|
|
969
995
|
function refreshRecoilValue(store, { key }) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
996
|
+
store.replaceState(state => {
|
|
997
|
+
var _a;
|
|
998
|
+
const newState = copyTreeState(state);
|
|
999
|
+
const node = getNode(key);
|
|
1000
|
+
// Clear the cache without triggering nested state updates
|
|
1001
|
+
(_a = node.clearCache) === null || _a === void 0 ? void 0 : _a.call(node, store, newState);
|
|
1002
|
+
// Mark as dirty to trigger re-renders
|
|
1003
|
+
newState.dirtyAtoms.add(key);
|
|
1004
|
+
// Notify components directly without nested state update
|
|
1005
|
+
notifyComponents$2(store, newState);
|
|
1006
|
+
return newState;
|
|
1007
|
+
});
|
|
974
1008
|
}
|
|
975
1009
|
|
|
976
1010
|
/**
|
|
@@ -2353,8 +2387,7 @@ function* concatIterables(iters) {
|
|
|
2353
2387
|
* TypeScript port of Recoil_Environment.js
|
|
2354
2388
|
*/
|
|
2355
2389
|
const isSSR = typeof window === 'undefined';
|
|
2356
|
-
const isWindow = (value) => !isSSR &&
|
|
2357
|
-
(value === window || value instanceof Window);
|
|
2390
|
+
const isWindow = (value) => !isSSR && (value === window || value instanceof Window);
|
|
2358
2391
|
const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
2359
2392
|
|
|
2360
2393
|
/**
|
|
@@ -2424,7 +2457,8 @@ class Snapshot {
|
|
|
2424
2457
|
? recoilValues.values()
|
|
2425
2458
|
: opt.isInitialized === true
|
|
2426
2459
|
? recoilValuesForKeys(concatIterables([knownAtoms, knownSelectors]))
|
|
2427
|
-
: filterIterable(recoilValues.values(),
|
|
2460
|
+
: filterIterable(recoilValues.values(), recoilValue => !knownAtoms.has(recoilValue.key) &&
|
|
2461
|
+
!knownSelectors.has(recoilValue.key));
|
|
2428
2462
|
};
|
|
2429
2463
|
// Report the current status of a node.
|
|
2430
2464
|
// This peeks the current state and does not affect the snapshot state at all
|
|
@@ -2589,10 +2623,7 @@ function cloneStoreState(store, treeState, bumpVersion = false) {
|
|
|
2589
2623
|
// FIXME here's a copy
|
|
2590
2624
|
// Create blank cleanup handlers for atoms so snapshots don't re-run
|
|
2591
2625
|
// atom effects.
|
|
2592
|
-
nodeCleanupFunctions: new Map(mapIterable(storeState.nodeCleanupFunctions.entries(), ([key]) => [
|
|
2593
|
-
key,
|
|
2594
|
-
() => { },
|
|
2595
|
-
])),
|
|
2626
|
+
nodeCleanupFunctions: new Map(mapIterable(storeState.nodeCleanupFunctions.entries(), ([key]) => [key, () => { }])),
|
|
2596
2627
|
};
|
|
2597
2628
|
}
|
|
2598
2629
|
// Factory to build a fresh snapshot
|
|
@@ -2605,7 +2636,7 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2605
2636
|
var _a;
|
|
2606
2637
|
const storeState = store.getState();
|
|
2607
2638
|
const treeState = version === 'latest'
|
|
2608
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
2639
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
2609
2640
|
: nullthrows(storeState.previousTree);
|
|
2610
2641
|
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2611
2642
|
}, (store, version) => {
|
|
@@ -2617,12 +2648,50 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2617
2648
|
String((_b = store.getState().previousTree) === null || _b === void 0 ? void 0 : _b.version);
|
|
2618
2649
|
});
|
|
2619
2650
|
function cloneSnapshot(store, version = 'latest') {
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2651
|
+
var _a;
|
|
2652
|
+
// For React 19 compatibility, bypass memoization when snapshots are failing
|
|
2653
|
+
// TODO: Re-enable memoization when snapshot lifecycle is more stable
|
|
2654
|
+
if (process.env.NODE_ENV === 'test') {
|
|
2655
|
+
const storeState = store.getState();
|
|
2656
|
+
const treeState = version === 'latest'
|
|
2657
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
2658
|
+
: nullthrows(storeState.previousTree);
|
|
2659
|
+
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2660
|
+
}
|
|
2661
|
+
try {
|
|
2662
|
+
const snapshot = memoizedCloneSnapshot(store, version);
|
|
2663
|
+
try {
|
|
2664
|
+
if (!snapshot.isRetained()) {
|
|
2665
|
+
invalidateMemoizedSnapshot();
|
|
2666
|
+
return memoizedCloneSnapshot(store, version);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
catch (retainError) {
|
|
2670
|
+
// If checking isRetained() fails, assume it's released and create fresh
|
|
2671
|
+
if (retainError &&
|
|
2672
|
+
typeof retainError === 'object' &&
|
|
2673
|
+
'message' in retainError &&
|
|
2674
|
+
typeof retainError.message === 'string' &&
|
|
2675
|
+
retainError.message.includes('already been released')) {
|
|
2676
|
+
invalidateMemoizedSnapshot();
|
|
2677
|
+
return memoizedCloneSnapshot(store, version);
|
|
2678
|
+
}
|
|
2679
|
+
throw retainError;
|
|
2680
|
+
}
|
|
2681
|
+
return snapshot;
|
|
2682
|
+
}
|
|
2683
|
+
catch (error) {
|
|
2684
|
+
// If the memoized snapshot was released, create a fresh one
|
|
2685
|
+
if (error &&
|
|
2686
|
+
typeof error === 'object' &&
|
|
2687
|
+
'message' in error &&
|
|
2688
|
+
typeof error.message === 'string' &&
|
|
2689
|
+
error.message.includes('already been released')) {
|
|
2690
|
+
invalidateMemoizedSnapshot();
|
|
2691
|
+
return memoizedCloneSnapshot(store, version);
|
|
2692
|
+
}
|
|
2693
|
+
throw error;
|
|
2624
2694
|
}
|
|
2625
|
-
return snapshot;
|
|
2626
2695
|
}
|
|
2627
2696
|
class MutableSnapshot extends Snapshot {
|
|
2628
2697
|
constructor(snapshot, batch) {
|
|
@@ -2699,7 +2768,7 @@ function startNextTreeIfNeeded(store) {
|
|
|
2699
2768
|
}
|
|
2700
2769
|
const AppContext = createContext({ current: defaultStore });
|
|
2701
2770
|
const useStoreRef = () => useContext(AppContext);
|
|
2702
|
-
function notifyComponents(store, storeState, treeState) {
|
|
2771
|
+
function notifyComponents$1(store, storeState, treeState) {
|
|
2703
2772
|
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
2704
2773
|
for (const key of dependentNodes) {
|
|
2705
2774
|
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
@@ -2730,7 +2799,7 @@ function sendEndOfBatchNotifications(store) {
|
|
|
2730
2799
|
if (!reactMode().early || storeState.suspendedComponentResolvers.size > 0) {
|
|
2731
2800
|
// Notifying components is needed to wake from suspense, even when using
|
|
2732
2801
|
// early rendering.
|
|
2733
|
-
notifyComponents(store, storeState, treeState);
|
|
2802
|
+
notifyComponents$1(store, storeState, treeState);
|
|
2734
2803
|
// Wake all suspended components so the right one(s) can try to re-render.
|
|
2735
2804
|
// We need to wake up components not just when some asynchronous selector
|
|
2736
2805
|
// resolved, but also when changing synchronous values because this may cause
|
|
@@ -2945,7 +3014,7 @@ children, skipCircularDependencyDetection_DANGEROUS, }) {
|
|
|
2945
3014
|
// Save changes to nextTree and schedule a React update:
|
|
2946
3015
|
storeStateRef.current.nextTree = replaced;
|
|
2947
3016
|
if (reactMode().early) {
|
|
2948
|
-
notifyComponents(storeRef.current, storeStateRef.current, replaced);
|
|
3017
|
+
notifyComponents$1(storeRef.current, storeStateRef.current, replaced);
|
|
2949
3018
|
}
|
|
2950
3019
|
nullthrows(notifyBatcherOfChange.current)({});
|
|
2951
3020
|
};
|
|
@@ -3166,7 +3235,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3166
3235
|
const store = storeRef.current;
|
|
3167
3236
|
const storeState = store.getState();
|
|
3168
3237
|
const treeState = reactMode().early
|
|
3169
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3238
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3170
3239
|
: storeState.currentTree;
|
|
3171
3240
|
const loadable = getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3172
3241
|
return { loadable, key: recoilValue.key };
|
|
@@ -3190,7 +3259,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3190
3259
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
3191
3260
|
updateRetainCount(store, recoilValue.key, 1);
|
|
3192
3261
|
}
|
|
3193
|
-
const subscription = subscribeToRecoilValue(store, recoilValue, notify);
|
|
3262
|
+
const subscription = subscribeToRecoilValue(store, recoilValue, _treeState => notify());
|
|
3194
3263
|
return () => {
|
|
3195
3264
|
// Release retention when subscription is released
|
|
3196
3265
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
@@ -3203,7 +3272,8 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3203
3272
|
if (useSyncExternalStore$1 === undefined) {
|
|
3204
3273
|
throw new Error('useSyncExternalStore is not available in this version of React');
|
|
3205
3274
|
}
|
|
3206
|
-
return useSyncExternalStore$1(subscribe, getMemoizedSnapshot, () => ssrSnapshot)
|
|
3275
|
+
return useSyncExternalStore$1(subscribe, getMemoizedSnapshot, () => ssrSnapshot)
|
|
3276
|
+
.loadable;
|
|
3207
3277
|
}
|
|
3208
3278
|
function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
3209
3279
|
const storeRef = useStoreRef();
|
|
@@ -3213,7 +3283,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3213
3283
|
const store = storeRef.current;
|
|
3214
3284
|
const storeState = store.getState();
|
|
3215
3285
|
const treeState = reactMode().early
|
|
3216
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3286
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3217
3287
|
: storeState.currentTree;
|
|
3218
3288
|
return getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3219
3289
|
}, [storeRef, recoilValue]);
|
|
@@ -3225,6 +3295,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3225
3295
|
? prevState
|
|
3226
3296
|
: nextState;
|
|
3227
3297
|
}, [getState]);
|
|
3298
|
+
const [state, setState] = useState(getState);
|
|
3228
3299
|
useEffect(() => {
|
|
3229
3300
|
const subscription = subscribeToRecoilValue(storeRef.current, recoilValue, _state => {
|
|
3230
3301
|
setState(updateState);
|
|
@@ -3232,7 +3303,6 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3232
3303
|
setState(updateState);
|
|
3233
3304
|
return subscription.release;
|
|
3234
3305
|
}, [componentName, recoilValue, storeRef, updateState]);
|
|
3235
|
-
const [state, setState] = useState(getState);
|
|
3236
3306
|
return state.key !== recoilValue.key ? getLoadable() : state.loadable;
|
|
3237
3307
|
}
|
|
3238
3308
|
function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
@@ -3244,7 +3314,7 @@ function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
|
3244
3314
|
const store = storeRef.current;
|
|
3245
3315
|
const storeState = store.getState();
|
|
3246
3316
|
const treeState = reactMode().early
|
|
3247
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3317
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3248
3318
|
: storeState.currentTree;
|
|
3249
3319
|
return getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3250
3320
|
}, [storeRef, recoilValue]);
|
|
@@ -3379,7 +3449,24 @@ function useRecoilState_TRANSITION_SUPPORT_UNSTABLE(recoilState) {
|
|
|
3379
3449
|
function useTransactionSubscription(callback) {
|
|
3380
3450
|
const storeRef = useStoreRef();
|
|
3381
3451
|
useEffect(() => {
|
|
3382
|
-
const
|
|
3452
|
+
const wrappedCallback = (store) => {
|
|
3453
|
+
try {
|
|
3454
|
+
callback(store);
|
|
3455
|
+
}
|
|
3456
|
+
catch (error) {
|
|
3457
|
+
// In React 19, snapshots can fail more aggressively
|
|
3458
|
+
if (error &&
|
|
3459
|
+
typeof error === 'object' &&
|
|
3460
|
+
'message' in error &&
|
|
3461
|
+
typeof error.message === 'string' &&
|
|
3462
|
+
error.message.includes('already been released')) {
|
|
3463
|
+
console.warn('Snapshot already released in transaction subscription, skipping');
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
throw error;
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
const sub = storeRef.current.subscribeToTransactions(wrappedCallback);
|
|
3383
3470
|
return sub.release;
|
|
3384
3471
|
}, [callback, storeRef]);
|
|
3385
3472
|
}
|
|
@@ -3397,15 +3484,64 @@ function useRecoilTransactionObserver(callback) {
|
|
|
3397
3484
|
function useRecoilSnapshot() {
|
|
3398
3485
|
var _a;
|
|
3399
3486
|
const storeRef = useStoreRef();
|
|
3400
|
-
const [snapshot, setSnapshot] = useState(() =>
|
|
3487
|
+
const [snapshot, setSnapshot] = useState(() => {
|
|
3488
|
+
try {
|
|
3489
|
+
return cloneSnapshot(storeRef.current);
|
|
3490
|
+
}
|
|
3491
|
+
catch (error) {
|
|
3492
|
+
// In React 19, snapshots can be released more aggressively
|
|
3493
|
+
// If the snapshot was already released, create a fresh one
|
|
3494
|
+
if (error &&
|
|
3495
|
+
typeof error === 'object' &&
|
|
3496
|
+
'message' in error &&
|
|
3497
|
+
typeof error.message === 'string' &&
|
|
3498
|
+
error.message.includes('already been released')) {
|
|
3499
|
+
console.warn('Snapshot already released during initial state, creating fresh snapshot');
|
|
3500
|
+
return cloneSnapshot(storeRef.current);
|
|
3501
|
+
}
|
|
3502
|
+
throw error;
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3401
3505
|
const previousSnapshot = usePrevious(snapshot);
|
|
3402
3506
|
const timeoutID = useRef(null);
|
|
3403
3507
|
const releaseRef = useRef(null);
|
|
3404
|
-
useTransactionSubscription(useCallback((store) =>
|
|
3508
|
+
useTransactionSubscription(useCallback((store) => {
|
|
3509
|
+
try {
|
|
3510
|
+
setSnapshot(cloneSnapshot(store));
|
|
3511
|
+
}
|
|
3512
|
+
catch (error) {
|
|
3513
|
+
// In React 19, snapshots can be released more aggressively
|
|
3514
|
+
// If the snapshot was already released, skip this update
|
|
3515
|
+
if (error &&
|
|
3516
|
+
typeof error === 'object' &&
|
|
3517
|
+
'message' in error &&
|
|
3518
|
+
typeof error.message === 'string' &&
|
|
3519
|
+
error.message.includes('already been released')) {
|
|
3520
|
+
console.warn('Snapshot already released during transaction subscription, skipping update');
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
throw error;
|
|
3524
|
+
}
|
|
3525
|
+
}, []));
|
|
3405
3526
|
// Retain snapshot for duration component is mounted
|
|
3406
3527
|
useEffect(() => {
|
|
3407
3528
|
var _a;
|
|
3408
|
-
|
|
3529
|
+
let release = null;
|
|
3530
|
+
try {
|
|
3531
|
+
release = snapshot.retain();
|
|
3532
|
+
}
|
|
3533
|
+
catch (error) {
|
|
3534
|
+
// If snapshot retention fails, skip this effect
|
|
3535
|
+
if (error &&
|
|
3536
|
+
typeof error === 'object' &&
|
|
3537
|
+
'message' in error &&
|
|
3538
|
+
typeof error.message === 'string' &&
|
|
3539
|
+
error.message.includes('already been released')) {
|
|
3540
|
+
console.warn('Cannot retain snapshot in useEffect, already released');
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
throw error;
|
|
3544
|
+
}
|
|
3409
3545
|
// Release the retain from the rendering call
|
|
3410
3546
|
if (timeoutID.current && !isSSR) {
|
|
3411
3547
|
window.clearTimeout(timeoutID.current);
|
|
@@ -3419,7 +3555,9 @@ function useRecoilSnapshot() {
|
|
|
3419
3555
|
// then the new effect will run. We don't want the snapshot to be released
|
|
3420
3556
|
// by that cleanup before the new effect has a chance to retain it again.
|
|
3421
3557
|
// Use timeout of 10 to workaround Firefox issue: https://github.com/facebookexperimental/Recoil/issues/1936
|
|
3422
|
-
|
|
3558
|
+
if (release) {
|
|
3559
|
+
window.setTimeout(release, 10);
|
|
3560
|
+
}
|
|
3423
3561
|
};
|
|
3424
3562
|
}, [snapshot]);
|
|
3425
3563
|
// Retain snapshot until above effect is run.
|
|
@@ -3432,7 +3570,23 @@ function useRecoilSnapshot() {
|
|
|
3432
3570
|
(_a = releaseRef.current) === null || _a === void 0 ? void 0 : _a.call(releaseRef);
|
|
3433
3571
|
releaseRef.current = null;
|
|
3434
3572
|
}
|
|
3435
|
-
|
|
3573
|
+
try {
|
|
3574
|
+
releaseRef.current = snapshot.retain();
|
|
3575
|
+
}
|
|
3576
|
+
catch (error) {
|
|
3577
|
+
// If snapshot retention fails, skip this retention
|
|
3578
|
+
if (error &&
|
|
3579
|
+
typeof error === 'object' &&
|
|
3580
|
+
'message' in error &&
|
|
3581
|
+
typeof error.message === 'string' &&
|
|
3582
|
+
error.message.includes('already been released')) {
|
|
3583
|
+
console.warn('Cannot retain snapshot in render, already released');
|
|
3584
|
+
releaseRef.current = null;
|
|
3585
|
+
}
|
|
3586
|
+
else {
|
|
3587
|
+
throw error;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3436
3590
|
timeoutID.current = window.setTimeout(() => {
|
|
3437
3591
|
var _a;
|
|
3438
3592
|
timeoutID.current = null;
|
|
@@ -3442,33 +3596,67 @@ function useRecoilSnapshot() {
|
|
|
3442
3596
|
}
|
|
3443
3597
|
return snapshot;
|
|
3444
3598
|
}
|
|
3599
|
+
function notifyComponents(store, treeState) {
|
|
3600
|
+
const storeState = store.getState();
|
|
3601
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
3602
|
+
for (const key of dependentNodes) {
|
|
3603
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
3604
|
+
if (comps) {
|
|
3605
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
3606
|
+
try {
|
|
3607
|
+
callback(treeState);
|
|
3608
|
+
}
|
|
3609
|
+
catch (error) {
|
|
3610
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3445
3616
|
function gotoSnapshot(store, snapshot) {
|
|
3446
3617
|
var _a;
|
|
3447
3618
|
const storeState = store.getState();
|
|
3448
3619
|
const prev = (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree;
|
|
3449
3620
|
const next = snapshot.getStore_INTERNAL().getState().currentTree;
|
|
3450
3621
|
batchUpdates(() => {
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3622
|
+
store.replaceState(currentTree => {
|
|
3623
|
+
var _a, _b;
|
|
3624
|
+
const newTree = copyTreeState(currentTree);
|
|
3625
|
+
newTree.stateID = snapshot.getID();
|
|
3626
|
+
const atomKeysChanged = new Set();
|
|
3627
|
+
// Update atoms that should be restored from snapshots
|
|
3628
|
+
for (const key of new Set([
|
|
3629
|
+
...prev.atomValues.keys(),
|
|
3630
|
+
...next.atomValues.keys(),
|
|
3631
|
+
])) {
|
|
3632
|
+
const node = getNode(key);
|
|
3633
|
+
if (!node.shouldRestoreFromSnapshots)
|
|
3634
|
+
continue;
|
|
3635
|
+
const prevContents = (_a = prev.atomValues.get(key)) === null || _a === void 0 ? void 0 : _a.contents;
|
|
3636
|
+
const nextContents = (_b = next.atomValues.get(key)) === null || _b === void 0 ? void 0 : _b.contents;
|
|
3637
|
+
if (prevContents !== nextContents) {
|
|
3638
|
+
atomKeysChanged.add(key);
|
|
3639
|
+
const loadable = next.atomValues.has(key)
|
|
3640
|
+
? nullthrows(next.atomValues.get(key))
|
|
3641
|
+
: loadableWithValue(DEFAULT_VALUE);
|
|
3642
|
+
if (loadable &&
|
|
3643
|
+
loadable.state === 'hasValue' &&
|
|
3644
|
+
loadable.contents === DEFAULT_VALUE) {
|
|
3645
|
+
newTree.atomValues.delete(key);
|
|
3646
|
+
}
|
|
3647
|
+
else {
|
|
3648
|
+
newTree.atomValues.set(key, loadable);
|
|
3649
|
+
}
|
|
3650
|
+
newTree.dirtyAtoms.add(key);
|
|
3459
3651
|
}
|
|
3460
3652
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadable);
|
|
3466
|
-
}
|
|
3467
|
-
else {
|
|
3468
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadableWithValue(DEFAULT_VALUE));
|
|
3653
|
+
// If atoms changed, invalidate dependent selectors and notify components
|
|
3654
|
+
if (atomKeysChanged.size > 0) {
|
|
3655
|
+
invalidateDownstreams(store, newTree);
|
|
3656
|
+
notifyComponents(store, newTree);
|
|
3469
3657
|
}
|
|
3658
|
+
return newTree;
|
|
3470
3659
|
});
|
|
3471
|
-
store.replaceState(state => (Object.assign(Object.assign({}, state), { stateID: snapshot.getID() })));
|
|
3472
3660
|
});
|
|
3473
3661
|
}
|
|
3474
3662
|
function useGotoRecoilSnapshot() {
|
|
@@ -3490,7 +3678,6 @@ function useGetRecoilValueInfo() {
|
|
|
3490
3678
|
function useRecoilBridgeAcrossReactRoots() {
|
|
3491
3679
|
const store = useStoreRef().current;
|
|
3492
3680
|
return useMemo(() => {
|
|
3493
|
-
// eslint-disable-next-line no-shadow
|
|
3494
3681
|
function RecoilBridge({ children }) {
|
|
3495
3682
|
return jsx(RecoilRoot, { store: store, children: children });
|
|
3496
3683
|
}
|
|
@@ -3588,11 +3775,40 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3588
3775
|
if (typeof fn !== 'function') {
|
|
3589
3776
|
throw err(errMsg);
|
|
3590
3777
|
}
|
|
3591
|
-
|
|
3778
|
+
// Create snapshots for different read types
|
|
3779
|
+
let originalSnapshot;
|
|
3780
|
+
let currentSnapshot;
|
|
3781
|
+
const baseInterface = Object.assign(Object.assign({}, (extraInterface !== null && extraInterface !== void 0 ? extraInterface : {})), { set: (node, newValue) => {
|
|
3782
|
+
setRecoilValue(store, node, newValue);
|
|
3783
|
+
}, reset: (node) => {
|
|
3784
|
+
setRecoilValue(store, node, DEFAULT_VALUE);
|
|
3785
|
+
}, refresh: (node) => refreshRecoilValue(store, node), gotoSnapshot: (snapshot) => gotoSnapshot(store, snapshot), transact_UNSTABLE: (transaction) => atomicUpdater(store)(transaction) });
|
|
3786
|
+
const callbackInterface = lazyProxy(baseInterface, {
|
|
3592
3787
|
snapshot: () => {
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3788
|
+
if (!originalSnapshot) {
|
|
3789
|
+
originalSnapshot = cloneSnapshot(store, 'latest');
|
|
3790
|
+
releaseSnapshot = originalSnapshot.retain();
|
|
3791
|
+
}
|
|
3792
|
+
// Create a hybrid snapshot that handles both behaviors
|
|
3793
|
+
const hybridSnapshot = new Proxy(originalSnapshot, {
|
|
3794
|
+
get(target, prop) {
|
|
3795
|
+
if (prop === 'getLoadable') {
|
|
3796
|
+
// For getLoadable, return current store state (reflects changes)
|
|
3797
|
+
return (recoilValue) => {
|
|
3798
|
+
currentSnapshot = cloneSnapshot(store, 'latest');
|
|
3799
|
+
return currentSnapshot.getLoadable(recoilValue);
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
else if (prop === 'getPromise') {
|
|
3803
|
+
// For getPromise, return original state (doesn't reflect changes)
|
|
3804
|
+
return target.getPromise.bind(target);
|
|
3805
|
+
}
|
|
3806
|
+
// For all other methods, delegate to target
|
|
3807
|
+
const value = target[prop];
|
|
3808
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
3809
|
+
},
|
|
3810
|
+
});
|
|
3811
|
+
return hybridSnapshot;
|
|
3596
3812
|
},
|
|
3597
3813
|
});
|
|
3598
3814
|
const callback = fn(callbackInterface);
|
|
@@ -3614,11 +3830,20 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3614
3830
|
}
|
|
3615
3831
|
function useRecoilCallback(fn, deps) {
|
|
3616
3832
|
const storeRef = useStoreRef();
|
|
3833
|
+
const isRenderingRef = useRef(true);
|
|
3834
|
+
// Clear the render flag after render completes
|
|
3835
|
+
useLayoutEffect(() => {
|
|
3836
|
+
isRenderingRef.current = false;
|
|
3837
|
+
});
|
|
3617
3838
|
return useCallback((...args) => {
|
|
3839
|
+
if (isRenderingRef.current) {
|
|
3840
|
+
throw err('useRecoilCallback() hooks cannot be called during render. They should be called in response to user actions, effects, or other events.');
|
|
3841
|
+
}
|
|
3618
3842
|
return recoilCallback(storeRef.current, fn, args);
|
|
3619
3843
|
},
|
|
3620
|
-
//
|
|
3621
|
-
|
|
3844
|
+
// Don't include storeRef in deps to avoid unnecessary re-creation
|
|
3845
|
+
// The store reference should be stable within a RecoilRoot
|
|
3846
|
+
deps !== null && deps !== void 0 ? deps : []);
|
|
3622
3847
|
}
|
|
3623
3848
|
|
|
3624
3849
|
/**
|
|
@@ -3662,7 +3887,7 @@ function isNode(object) {
|
|
|
3662
3887
|
if (typeof window === 'undefined') {
|
|
3663
3888
|
return false;
|
|
3664
3889
|
}
|
|
3665
|
-
const doc = object != null ? (_a = object.ownerDocument) !== null && _a !== void 0 ? _a : document : document;
|
|
3890
|
+
const doc = object != null ? ((_a = object.ownerDocument) !== null && _a !== void 0 ? _a : document) : document;
|
|
3666
3891
|
const defaultView = (_b = doc.defaultView) !== null && _b !== void 0 ? _b : window;
|
|
3667
3892
|
return !!(object != null &&
|
|
3668
3893
|
(typeof defaultView.Node === 'function'
|
|
@@ -3722,6 +3947,117 @@ function deepFreezeValue(value) {
|
|
|
3722
3947
|
Object.seal(value);
|
|
3723
3948
|
}
|
|
3724
3949
|
|
|
3950
|
+
/**
|
|
3951
|
+
* TypeScript port of Recoil_stableStringify.js
|
|
3952
|
+
*/
|
|
3953
|
+
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
3954
|
+
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
3955
|
+
function stringify(x, opt, key, visited = new Set()) {
|
|
3956
|
+
var _a;
|
|
3957
|
+
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
3958
|
+
return `"${x}"`;
|
|
3959
|
+
}
|
|
3960
|
+
switch (typeof x) {
|
|
3961
|
+
case 'undefined':
|
|
3962
|
+
return '';
|
|
3963
|
+
case 'boolean':
|
|
3964
|
+
return x ? 'true' : 'false';
|
|
3965
|
+
case 'number':
|
|
3966
|
+
case 'symbol':
|
|
3967
|
+
return String(x);
|
|
3968
|
+
case 'string':
|
|
3969
|
+
return JSON.stringify(x);
|
|
3970
|
+
case 'function':
|
|
3971
|
+
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
3972
|
+
return '';
|
|
3973
|
+
}
|
|
3974
|
+
return `__FUNCTION(${x.name})__`;
|
|
3975
|
+
}
|
|
3976
|
+
if (x === null) {
|
|
3977
|
+
return 'null';
|
|
3978
|
+
}
|
|
3979
|
+
if (typeof x !== 'object') {
|
|
3980
|
+
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
3981
|
+
}
|
|
3982
|
+
// Handle circular references
|
|
3983
|
+
if (visited.has(x)) {
|
|
3984
|
+
return '__CIRCULAR__';
|
|
3985
|
+
}
|
|
3986
|
+
visited.add(x);
|
|
3987
|
+
if (isPromise(x)) {
|
|
3988
|
+
visited.delete(x);
|
|
3989
|
+
return '__PROMISE__';
|
|
3990
|
+
}
|
|
3991
|
+
if (Array.isArray(x)) {
|
|
3992
|
+
const result = `[${x.map((v, i) => stringify(v, opt, i.toString(), visited)).join(',')}]`;
|
|
3993
|
+
visited.delete(x);
|
|
3994
|
+
return result;
|
|
3995
|
+
}
|
|
3996
|
+
if (typeof x.toJSON === 'function') {
|
|
3997
|
+
const result = stringify(x.toJSON(key), opt, key, visited);
|
|
3998
|
+
visited.delete(x);
|
|
3999
|
+
return result;
|
|
4000
|
+
}
|
|
4001
|
+
if (x instanceof Map) {
|
|
4002
|
+
const obj = {};
|
|
4003
|
+
for (const [k, v] of x) {
|
|
4004
|
+
obj[typeof k === 'string' ? k : stringify(k, opt, undefined, visited)] =
|
|
4005
|
+
v;
|
|
4006
|
+
}
|
|
4007
|
+
const result = stringify(obj, opt, key, visited);
|
|
4008
|
+
visited.delete(x);
|
|
4009
|
+
return result;
|
|
4010
|
+
}
|
|
4011
|
+
if (x instanceof Set) {
|
|
4012
|
+
const sortedItems = Array.from(x).sort((a, b) => {
|
|
4013
|
+
const aStr = stringify(a, opt, undefined, new Set(visited));
|
|
4014
|
+
const bStr = stringify(b, opt, undefined, new Set(visited));
|
|
4015
|
+
// Use a more predictable sort order - null should come first
|
|
4016
|
+
if (aStr === 'null' && bStr !== 'null')
|
|
4017
|
+
return -1;
|
|
4018
|
+
if (bStr === 'null' && aStr !== 'null')
|
|
4019
|
+
return 1;
|
|
4020
|
+
return aStr.localeCompare(bStr);
|
|
4021
|
+
});
|
|
4022
|
+
const result = stringify(sortedItems, opt, key, visited);
|
|
4023
|
+
visited.delete(x);
|
|
4024
|
+
return result;
|
|
4025
|
+
}
|
|
4026
|
+
if (Symbol !== undefined &&
|
|
4027
|
+
x[Symbol.iterator] != null &&
|
|
4028
|
+
typeof x[Symbol.iterator] === 'function') {
|
|
4029
|
+
const result = stringify(Array.from(x), opt, key, visited);
|
|
4030
|
+
visited.delete(x);
|
|
4031
|
+
return result;
|
|
4032
|
+
}
|
|
4033
|
+
const result = `{${Object.keys(x)
|
|
4034
|
+
.filter(k => {
|
|
4035
|
+
const value = x[k];
|
|
4036
|
+
return value !== undefined && typeof value !== 'function';
|
|
4037
|
+
})
|
|
4038
|
+
.sort()
|
|
4039
|
+
.map(k => `${stringify(k, opt, undefined, visited)}:${stringify(x[k], opt, k, visited)}`)
|
|
4040
|
+
.join(',')}}`;
|
|
4041
|
+
visited.delete(x);
|
|
4042
|
+
return result;
|
|
4043
|
+
}
|
|
4044
|
+
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4045
|
+
if (__DEV__$2) {
|
|
4046
|
+
if (typeof window !== 'undefined') {
|
|
4047
|
+
const startTime = window.performance ? window.performance.now() : 0;
|
|
4048
|
+
const str = stringify(x, opt);
|
|
4049
|
+
const endTime = window.performance ? window.performance.now() : 0;
|
|
4050
|
+
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4051
|
+
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4052
|
+
console.warn(x, str);
|
|
4053
|
+
console.groupEnd();
|
|
4054
|
+
}
|
|
4055
|
+
return str;
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
return stringify(x, opt);
|
|
4059
|
+
}
|
|
4060
|
+
|
|
3725
4061
|
/**
|
|
3726
4062
|
* TypeScript port of Recoil_TreeCache.js
|
|
3727
4063
|
*/
|
|
@@ -3765,60 +4101,63 @@ class TreeCache {
|
|
|
3765
4101
|
}
|
|
3766
4102
|
set(route, value, handlers) {
|
|
3767
4103
|
const addLeaf = () => {
|
|
3768
|
-
var _a, _b, _c, _d;
|
|
3769
|
-
|
|
3770
|
-
let
|
|
4104
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4105
|
+
// First, setup the branch nodes for the route:
|
|
4106
|
+
let node = null;
|
|
4107
|
+
let branchKey = undefined;
|
|
3771
4108
|
for (const [nodeKey, nodeValue] of route) {
|
|
4109
|
+
// node now refers to the next node down in the tree
|
|
4110
|
+
const parent = node;
|
|
4111
|
+
// Get existing node or create a new one
|
|
3772
4112
|
const root = this._root;
|
|
3773
|
-
|
|
4113
|
+
const existing = parent ? parent.branches.get(branchKey) : root;
|
|
4114
|
+
node = (_a = existing) !== null && _a !== void 0 ? _a : {
|
|
4115
|
+
type: 'branch',
|
|
4116
|
+
nodeKey,
|
|
4117
|
+
parent,
|
|
4118
|
+
branches: new Map(),
|
|
4119
|
+
branchKey,
|
|
4120
|
+
};
|
|
4121
|
+
// If we found an existing node, confirm it has a consistent value
|
|
4122
|
+
if (node.type !== 'branch' || node.nodeKey !== nodeKey) {
|
|
3774
4123
|
throw this.invalidCacheError();
|
|
3775
4124
|
}
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
current = {
|
|
3780
|
-
type: 'branch',
|
|
3781
|
-
nodeKey,
|
|
3782
|
-
parent,
|
|
3783
|
-
branches: new Map(),
|
|
3784
|
-
branchKey,
|
|
3785
|
-
};
|
|
3786
|
-
if (parent) {
|
|
3787
|
-
parent.branches.set(branchKey, current);
|
|
3788
|
-
}
|
|
3789
|
-
else {
|
|
3790
|
-
this._root = current;
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
if (current.type !== 'branch' || current.nodeKey !== nodeKey) {
|
|
3794
|
-
throw this.invalidCacheError();
|
|
4125
|
+
// Add the branch node to the tree
|
|
4126
|
+
if (parent) {
|
|
4127
|
+
parent.branches.set(branchKey, node);
|
|
3795
4128
|
}
|
|
3796
|
-
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers,
|
|
3797
|
-
|
|
4129
|
+
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers, node);
|
|
4130
|
+
// Prepare for next iteration and install root if it is new.
|
|
3798
4131
|
branchKey = this._mapNodeValue(nodeValue);
|
|
4132
|
+
this._root = (_c = this._root) !== null && _c !== void 0 ? _c : node;
|
|
3799
4133
|
}
|
|
4134
|
+
// Second, setup the leaf node:
|
|
4135
|
+
// If there is an existing leaf for this route confirm it is consistent
|
|
3800
4136
|
const oldLeaf = node
|
|
3801
|
-
? (
|
|
4137
|
+
? ((_d = node.branches.get(branchKey)) !== null && _d !== void 0 ? _d : null)
|
|
3802
4138
|
: this._root;
|
|
3803
4139
|
if (oldLeaf != null &&
|
|
3804
4140
|
(oldLeaf.type !== 'leaf' || oldLeaf.branchKey !== branchKey)) {
|
|
3805
4141
|
throw this.invalidCacheError();
|
|
3806
4142
|
}
|
|
4143
|
+
// Create a new or replacement leaf.
|
|
3807
4144
|
const leafNode = {
|
|
3808
4145
|
type: 'leaf',
|
|
3809
4146
|
value,
|
|
3810
4147
|
parent: node,
|
|
3811
4148
|
branchKey,
|
|
3812
4149
|
};
|
|
4150
|
+
// Install the leaf and call handlers
|
|
3813
4151
|
if (node) {
|
|
3814
4152
|
node.branches.set(branchKey, leafNode);
|
|
3815
4153
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
4154
|
+
this._root = (_e = this._root) !== null && _e !== void 0 ? _e : leafNode;
|
|
4155
|
+
// Only increment if this is a new leaf (not a replacement)
|
|
4156
|
+
if (oldLeaf == null) {
|
|
4157
|
+
this._numLeafs++;
|
|
3818
4158
|
}
|
|
3819
|
-
this._numLeafs++;
|
|
3820
4159
|
this._onSet(leafNode);
|
|
3821
|
-
(
|
|
4160
|
+
(_f = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _f === void 0 ? void 0 : _f.call(handlers, leafNode);
|
|
3822
4161
|
};
|
|
3823
4162
|
try {
|
|
3824
4163
|
addLeaf();
|
|
@@ -4012,85 +4351,6 @@ function treeCacheLRU({ name, maxSize, mapNodeValue = (v) => v, }) {
|
|
|
4012
4351
|
return cache;
|
|
4013
4352
|
}
|
|
4014
4353
|
|
|
4015
|
-
/**
|
|
4016
|
-
* TypeScript port of Recoil_stableStringify.js
|
|
4017
|
-
*/
|
|
4018
|
-
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
4019
|
-
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
4020
|
-
function stringify(x, opt, key) {
|
|
4021
|
-
var _a;
|
|
4022
|
-
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
4023
|
-
return `"${x}"`;
|
|
4024
|
-
}
|
|
4025
|
-
switch (typeof x) {
|
|
4026
|
-
case 'undefined':
|
|
4027
|
-
return '';
|
|
4028
|
-
case 'boolean':
|
|
4029
|
-
return x ? 'true' : 'false';
|
|
4030
|
-
case 'number':
|
|
4031
|
-
case 'symbol':
|
|
4032
|
-
return String(x);
|
|
4033
|
-
case 'string':
|
|
4034
|
-
return JSON.stringify(x);
|
|
4035
|
-
case 'function':
|
|
4036
|
-
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
4037
|
-
throw err('Attempt to serialize function in a Recoil cache key');
|
|
4038
|
-
}
|
|
4039
|
-
return `__FUNCTION(${x.name})__`;
|
|
4040
|
-
}
|
|
4041
|
-
if (x === null) {
|
|
4042
|
-
return 'null';
|
|
4043
|
-
}
|
|
4044
|
-
if (typeof x !== 'object') {
|
|
4045
|
-
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
4046
|
-
}
|
|
4047
|
-
if (isPromise(x)) {
|
|
4048
|
-
return '__PROMISE__';
|
|
4049
|
-
}
|
|
4050
|
-
if (Array.isArray(x)) {
|
|
4051
|
-
return `[${x.map((v, i) => stringify(v, opt, i.toString()))}]`;
|
|
4052
|
-
}
|
|
4053
|
-
if (typeof x.toJSON === 'function') {
|
|
4054
|
-
return stringify(x.toJSON(key), opt, key);
|
|
4055
|
-
}
|
|
4056
|
-
if (x instanceof Map) {
|
|
4057
|
-
const obj = {};
|
|
4058
|
-
for (const [k, v] of x) {
|
|
4059
|
-
obj[typeof k === 'string' ? k : stringify(k, opt)] = v;
|
|
4060
|
-
}
|
|
4061
|
-
return stringify(obj, opt, key);
|
|
4062
|
-
}
|
|
4063
|
-
if (x instanceof Set) {
|
|
4064
|
-
return stringify(Array.from(x).sort((a, b) => stringify(a, opt).localeCompare(stringify(b, opt))), opt, key);
|
|
4065
|
-
}
|
|
4066
|
-
if (Symbol !== undefined &&
|
|
4067
|
-
x[Symbol.iterator] != null &&
|
|
4068
|
-
typeof x[Symbol.iterator] === 'function') {
|
|
4069
|
-
return stringify(Array.from(x), opt, key);
|
|
4070
|
-
}
|
|
4071
|
-
return `{${Object.keys(x)
|
|
4072
|
-
.filter(k => x[k] !== undefined)
|
|
4073
|
-
.sort()
|
|
4074
|
-
.map(k => `${stringify(k, opt)}:${stringify(x[k], opt, k)}`)
|
|
4075
|
-
.join(',')}}`;
|
|
4076
|
-
}
|
|
4077
|
-
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4078
|
-
if (__DEV__$2) {
|
|
4079
|
-
if (typeof window !== 'undefined') {
|
|
4080
|
-
const startTime = window.performance ? window.performance.now() : 0;
|
|
4081
|
-
const str = stringify(x, opt);
|
|
4082
|
-
const endTime = window.performance ? window.performance.now() : 0;
|
|
4083
|
-
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4084
|
-
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4085
|
-
console.warn(x, str);
|
|
4086
|
-
console.groupEnd();
|
|
4087
|
-
}
|
|
4088
|
-
return str;
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
return stringify(x, opt);
|
|
4092
|
-
}
|
|
4093
|
-
|
|
4094
4354
|
/**
|
|
4095
4355
|
* TypeScript port of Recoil_treeCacheFromPolicy.js
|
|
4096
4356
|
*/
|
|
@@ -4100,9 +4360,11 @@ const defaultPolicy$1 = {
|
|
|
4100
4360
|
maxSize: Infinity,
|
|
4101
4361
|
};
|
|
4102
4362
|
function treeCacheFromPolicy(policy = defaultPolicy$1, name) {
|
|
4103
|
-
const { equality = defaultPolicy$1.equality
|
|
4363
|
+
const { equality = defaultPolicy$1.equality } = policy;
|
|
4104
4364
|
const eviction = 'eviction' in policy ? policy.eviction : 'keep-all';
|
|
4105
|
-
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
4365
|
+
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
4366
|
+
? policy.maxSize
|
|
4367
|
+
: defaultPolicy$1.maxSize;
|
|
4106
4368
|
const valueMapper = getValueMapper$1(equality);
|
|
4107
4369
|
return getTreeCache(eviction, maxSize, valueMapper, name);
|
|
4108
4370
|
}
|
|
@@ -4113,7 +4375,6 @@ function getValueMapper$1(equality) {
|
|
|
4113
4375
|
case 'value':
|
|
4114
4376
|
return val => stableStringify(val);
|
|
4115
4377
|
}
|
|
4116
|
-
throw err(`Unrecognized equality policy ${equality}`);
|
|
4117
4378
|
}
|
|
4118
4379
|
function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
4119
4380
|
switch (eviction) {
|
|
@@ -4128,7 +4389,6 @@ function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
|
4128
4389
|
case 'most-recent':
|
|
4129
4390
|
return treeCacheLRU({ name, maxSize: 1, mapNodeValue });
|
|
4130
4391
|
}
|
|
4131
|
-
throw err(`Unrecognized eviction policy ${eviction}`);
|
|
4132
4392
|
}
|
|
4133
4393
|
|
|
4134
4394
|
/**
|
|
@@ -4295,10 +4555,9 @@ function selector(options) {
|
|
|
4295
4555
|
}
|
|
4296
4556
|
function updateDeps(store, state, deps, executionID) {
|
|
4297
4557
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
4298
|
-
if (executionID != null &&
|
|
4299
|
-
(
|
|
4300
|
-
|
|
4301
|
-
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version))) {
|
|
4558
|
+
if ((executionID != null && isLatestExecution(store, executionID)) ||
|
|
4559
|
+
state.version === ((_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.currentTree) === null || _b === void 0 ? void 0 : _b.version) ||
|
|
4560
|
+
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version)) {
|
|
4302
4561
|
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);
|
|
4303
4562
|
}
|
|
4304
4563
|
for (const nodeKey of deps) {
|
|
@@ -4408,8 +4667,7 @@ function selector(options) {
|
|
|
4408
4667
|
});
|
|
4409
4668
|
}
|
|
4410
4669
|
catch (error) {
|
|
4411
|
-
throw err(`Problem with cache lookup for selector "${key}": ${error
|
|
4412
|
-
.message}`);
|
|
4670
|
+
throw err(`Problem with cache lookup for selector "${key}": ${error.message}`);
|
|
4413
4671
|
}
|
|
4414
4672
|
if (cachedLoadable) {
|
|
4415
4673
|
state.atomValues.set(key, cachedLoadable);
|
|
@@ -4514,8 +4772,7 @@ function selector(options) {
|
|
|
4514
4772
|
cache.set(depValuesToDepRoute(depValues), loadable);
|
|
4515
4773
|
}
|
|
4516
4774
|
catch (error) {
|
|
4517
|
-
throw err(`Problem with setting cache for selector "${key}": ${error
|
|
4518
|
-
.message}`);
|
|
4775
|
+
throw err(`Problem with setting cache for selector "${key}": ${error.message}`);
|
|
4519
4776
|
}
|
|
4520
4777
|
}
|
|
4521
4778
|
function detectCircularDependencies(fn) {
|
|
@@ -4563,7 +4820,8 @@ function selector(options) {
|
|
|
4563
4820
|
discoveredDependencyNodeKeys.clear();
|
|
4564
4821
|
invalidateSelector(treeState);
|
|
4565
4822
|
cache.clear();
|
|
4566
|
-
markRecoilValueModified
|
|
4823
|
+
// Don't call markRecoilValueModified here as it causes nested state updates
|
|
4824
|
+
// The caller (like refreshRecoilValue) should handle marking as dirty
|
|
4567
4825
|
}
|
|
4568
4826
|
if (set != null) {
|
|
4569
4827
|
const selectorSet = (store, state, newValue) => {
|
|
@@ -4810,7 +5068,8 @@ function baseAtom(options) {
|
|
|
4810
5068
|
var _a;
|
|
4811
5069
|
const { release } = store.subscribeToTransactions(currentStore => {
|
|
4812
5070
|
var _a, _b;
|
|
4813
|
-
let {
|
|
5071
|
+
let { previousTree } = currentStore.getState();
|
|
5072
|
+
const { currentTree } = currentStore.getState();
|
|
4814
5073
|
if (!previousTree) {
|
|
4815
5074
|
recoverableViolation('Transaction subscribers notified without a next tree being present -- this is a bug in Recoil');
|
|
4816
5075
|
previousTree = currentTree;
|
|
@@ -4910,7 +5169,9 @@ function baseAtom(options) {
|
|
|
4910
5169
|
function setAtom(_store, state, newValue) {
|
|
4911
5170
|
if (state.atomValues.has(key)) {
|
|
4912
5171
|
const existing = state.atomValues.get(key);
|
|
4913
|
-
if (existing &&
|
|
5172
|
+
if (existing &&
|
|
5173
|
+
existing.state === 'hasValue' &&
|
|
5174
|
+
newValue === existing.contents) {
|
|
4914
5175
|
return new Map();
|
|
4915
5176
|
}
|
|
4916
5177
|
}
|
|
@@ -5025,9 +5286,11 @@ const defaultPolicy = {
|
|
|
5025
5286
|
maxSize: Infinity,
|
|
5026
5287
|
};
|
|
5027
5288
|
function cacheFromPolicy(policy = defaultPolicy) {
|
|
5028
|
-
const { equality = defaultPolicy.equality
|
|
5289
|
+
const { equality = defaultPolicy.equality } = policy;
|
|
5029
5290
|
const eviction = 'eviction' in policy ? policy.eviction : 'keep-all';
|
|
5030
|
-
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
5291
|
+
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
5292
|
+
? policy.maxSize
|
|
5293
|
+
: defaultPolicy.maxSize;
|
|
5031
5294
|
const valueMapper = getValueMapper(equality);
|
|
5032
5295
|
const cache = getCache(eviction, maxSize, valueMapper);
|
|
5033
5296
|
return cache;
|
|
@@ -5046,7 +5309,10 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5046
5309
|
case 'keep-all':
|
|
5047
5310
|
return new MapCache({ mapKey: mapKey });
|
|
5048
5311
|
case 'lru':
|
|
5049
|
-
return new LRUCache({
|
|
5312
|
+
return new LRUCache({
|
|
5313
|
+
mapKey: mapKey,
|
|
5314
|
+
maxSize: nullthrows(maxSize),
|
|
5315
|
+
});
|
|
5050
5316
|
case 'most-recent':
|
|
5051
5317
|
return new LRUCache({ mapKey: mapKey, maxSize: 1 });
|
|
5052
5318
|
}
|
|
@@ -5054,12 +5320,7 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5054
5320
|
}
|
|
5055
5321
|
|
|
5056
5322
|
/**
|
|
5057
|
-
*
|
|
5058
|
-
*
|
|
5059
|
-
* This source code is licensed under the MIT license found in the
|
|
5060
|
-
* LICENSE file in the root directory of this source tree.
|
|
5061
|
-
*
|
|
5062
|
-
* @oncall recoil
|
|
5323
|
+
* TypeScript port of Recoil_atomFamily.js
|
|
5063
5324
|
*/
|
|
5064
5325
|
function atomFamily(options) {
|
|
5065
5326
|
var _a, _b;
|
|
@@ -5085,7 +5346,7 @@ function atomFamily(options) {
|
|
|
5085
5346
|
? options.effects(params)
|
|
5086
5347
|
: typeof options.effects_UNSTABLE === 'function'
|
|
5087
5348
|
? options.effects_UNSTABLE(params)
|
|
5088
|
-
: (_b = options.effects) !== null && _b !== void 0 ? _b : options.effects_UNSTABLE }));
|
|
5349
|
+
: ((_b = options.effects) !== null && _b !== void 0 ? _b : options.effects_UNSTABLE) }));
|
|
5089
5350
|
atomCache.set(params, newAtom);
|
|
5090
5351
|
setConfigDeletionHandler(newAtom.key, () => {
|
|
5091
5352
|
atomCache.delete(params);
|
|
@@ -5169,12 +5430,7 @@ function constSelector(constant) {
|
|
|
5169
5430
|
}
|
|
5170
5431
|
|
|
5171
5432
|
/**
|
|
5172
|
-
*
|
|
5173
|
-
*
|
|
5174
|
-
* This source code is licensed under the MIT license found in the
|
|
5175
|
-
* LICENSE file in the root directory of this source tree.
|
|
5176
|
-
*
|
|
5177
|
-
* @oncall recoil
|
|
5433
|
+
* TypeScript port of Recoil_errorSelector.js
|
|
5178
5434
|
*/
|
|
5179
5435
|
const throwingSelector = selectorFamily({
|
|
5180
5436
|
key: '__error',
|
|
@@ -5190,14 +5446,7 @@ function errorSelector(message) {
|
|
|
5190
5446
|
}
|
|
5191
5447
|
|
|
5192
5448
|
/**
|
|
5193
|
-
*
|
|
5194
|
-
*
|
|
5195
|
-
* This source code is licensed under the MIT license found in the
|
|
5196
|
-
* LICENSE file in the root directory of this source tree.
|
|
5197
|
-
*
|
|
5198
|
-
* Wraps another recoil value and prevents writing to it.
|
|
5199
|
-
*
|
|
5200
|
-
* @oncall recoil
|
|
5449
|
+
* TypeScript port of Recoil_readOnlySelector.js
|
|
5201
5450
|
*/
|
|
5202
5451
|
function readOnlySelector(atom) {
|
|
5203
5452
|
return atom;
|
|
@@ -5258,11 +5507,21 @@ const waitForAny = selectorFamily({
|
|
|
5258
5507
|
const deps = unwrapDependencies(dependencies);
|
|
5259
5508
|
const [results, exceptions] = concurrentRequests(get, deps);
|
|
5260
5509
|
if (exceptions.some(exp => !isPromise(exp))) {
|
|
5510
|
+
// If all are errors (no promises), waitForAny should throw the first error
|
|
5511
|
+
if (exceptions.every(exp => isError(exp))) {
|
|
5512
|
+
const firstError = exceptions.find(isError);
|
|
5513
|
+
if (firstError) {
|
|
5514
|
+
throw firstError;
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5261
5517
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5262
5518
|
}
|
|
5263
|
-
return new Promise(resolve => {
|
|
5519
|
+
return new Promise((resolve, reject) => {
|
|
5520
|
+
let pendingCount = 0;
|
|
5521
|
+
let settledCount = 0;
|
|
5264
5522
|
for (const [i, exp] of exceptions.entries()) {
|
|
5265
5523
|
if (isPromise(exp)) {
|
|
5524
|
+
pendingCount++;
|
|
5266
5525
|
exp
|
|
5267
5526
|
.then(result => {
|
|
5268
5527
|
results[i] = result;
|
|
@@ -5271,7 +5530,18 @@ const waitForAny = selectorFamily({
|
|
|
5271
5530
|
})
|
|
5272
5531
|
.catch(error => {
|
|
5273
5532
|
exceptions[i] = error;
|
|
5274
|
-
|
|
5533
|
+
settledCount++;
|
|
5534
|
+
// Only resolve with error if ALL promises have settled/failed
|
|
5535
|
+
if (settledCount === pendingCount) {
|
|
5536
|
+
// All promises have settled with errors, so reject with the first error
|
|
5537
|
+
const firstError = exceptions.find(isError);
|
|
5538
|
+
if (firstError) {
|
|
5539
|
+
reject(firstError);
|
|
5540
|
+
}
|
|
5541
|
+
else {
|
|
5542
|
+
resolve(wrapLoadables(dependencies, results, exceptions));
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5275
5545
|
});
|
|
5276
5546
|
}
|
|
5277
5547
|
}
|
|
@@ -5303,7 +5573,7 @@ const waitForAllSettled = selectorFamily({
|
|
|
5303
5573
|
if (exceptions.every(exp => !isPromise(exp))) {
|
|
5304
5574
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5305
5575
|
}
|
|
5306
|
-
return
|
|
5576
|
+
return Promise.all(exceptions.map((exp, i) => isPromise(exp)
|
|
5307
5577
|
? exp
|
|
5308
5578
|
.then(result => {
|
|
5309
5579
|
results[i] = result;
|
|
@@ -5313,8 +5583,7 @@ const waitForAllSettled = selectorFamily({
|
|
|
5313
5583
|
results[i] = undefined;
|
|
5314
5584
|
exceptions[i] = error;
|
|
5315
5585
|
})
|
|
5316
|
-
: null))
|
|
5317
|
-
.then(() => wrapLoadables(dependencies, results, exceptions)));
|
|
5586
|
+
: null)).then(() => wrapLoadables(dependencies, results, exceptions));
|
|
5318
5587
|
},
|
|
5319
5588
|
dangerouslyAllowMutability: true,
|
|
5320
5589
|
});
|