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/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 (_) { /* T-ignore */ }
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 && other.contents === this.contents;
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.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
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 && !isSupported && !ReactRendererVersionMismatchWarnOnce) {
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' && loadable.contents instanceof DefaultValue) {
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' && valueOrUpdater !== DEFAULT_VALUE) {
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
- var _a;
971
- const { currentTree } = store.getState();
972
- const node = getNode(key);
973
- (_a = node.clearCache) === null || _a === void 0 ? void 0 : _a.call(node, store, currentTree);
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(), (recoilValue) => !knownAtoms.has(recoilValue.key) && !knownSelectors.has(recoilValue.key));
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
- const snapshot = memoizedCloneSnapshot(store, version);
2621
- if (!snapshot.isRetained()) {
2622
- invalidateMemoizedSnapshot();
2623
- return memoizedCloneSnapshot(store, version);
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).loadable;
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 sub = storeRef.current.subscribeToTransactions(callback);
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(() => cloneSnapshot(storeRef.current));
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) => setSnapshot(cloneSnapshot(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
- const release = snapshot.retain();
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
- window.setTimeout(release, 10);
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
- releaseRef.current = snapshot.retain();
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
- var _a, _b;
3452
- const keysToUpdate = new Set();
3453
- for (const keys of [prev.atomValues.keys(), next.atomValues.keys()]) {
3454
- for (const key of keys) {
3455
- if (((_a = prev.atomValues.get(key)) === null || _a === void 0 ? void 0 : _a.contents) !==
3456
- ((_b = next.atomValues.get(key)) === null || _b === void 0 ? void 0 : _b.contents) &&
3457
- getNode(key).shouldRestoreFromSnapshots) {
3458
- keysToUpdate.add(key);
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
- keysToUpdate.forEach(key => {
3463
- const loadable = next.atomValues.get(key);
3464
- if (loadable) {
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
- const callbackInterface = lazyProxy(Object.assign(Object.assign({}, (extraInterface !== null && extraInterface !== void 0 ? extraInterface : {})), { set: (node, newValue) => setRecoilValue(store, node, newValue), reset: (node) => setRecoilValue(store, node, DEFAULT_VALUE), refresh: (node) => refreshRecoilValue(store, node), gotoSnapshot: (snapshot) => gotoSnapshot(store, snapshot), transact_UNSTABLE: (transaction) => atomicUpdater(store)(transaction) }), {
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
- const snapshot = cloneSnapshot(store);
3594
- releaseSnapshot = snapshot.retain();
3595
- return snapshot;
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
- // eslint-disable-next-line fb-www/react-hooks-deps
3621
- deps != null ? [...deps, storeRef] : [storeRef]);
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
- let node = this._root;
3770
- let branchKey;
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
- if ((root === null || root === void 0 ? void 0 : root.type) === 'leaf') {
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
- const parent = node;
3777
- let current = parent ? (_a = parent.branches.get(branchKey)) !== null && _a !== void 0 ? _a : null : root;
3778
- if (!current) {
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, current);
3797
- node = current;
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
- ? (_c = node.branches.get(branchKey)) !== null && _c !== void 0 ? _c : null
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
- else {
3817
- this._root = leafNode;
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
- (_d = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _d === void 0 ? void 0 : _d.call(handlers, leafNode);
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, } = policy;
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' ? policy.maxSize : defaultPolicy$1.maxSize;
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
- (isLatestExecution(store, executionID) ||
4300
- state.version === ((_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.currentTree) === null || _b === void 0 ? void 0 : _b.version) ||
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(store, recoilValue);
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 { currentTree, previousTree } = currentStore.getState();
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 && existing.state === 'hasValue' && newValue === existing.contents) {
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, } = policy;
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' ? policy.maxSize : defaultPolicy.maxSize;
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({ mapKey: mapKey, maxSize: nullthrows(maxSize) });
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
- * Copyright (c) Meta Platforms, Inc. and affiliates.
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
- * Copyright (c) Meta Platforms, Inc. and affiliates.
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
- * Copyright (c) Meta Platforms, Inc. and affiliates.
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
- resolve(wrapLoadables(dependencies, results, exceptions));
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 (Promise.all(exceptions.map((exp, i) => isPromise(exp)
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
  });