react-native-onyx 2.0.134 → 2.0.136

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.
@@ -42,6 +42,7 @@ const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
42
42
  const Str = __importStar(require("./Str"));
43
43
  const utils_1 = __importDefault(require("./utils"));
44
44
  const OnyxCache_1 = __importDefault(require("./OnyxCache"));
45
+ const OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache"));
45
46
  /**
46
47
  * Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers.
47
48
  */
@@ -182,12 +183,16 @@ class OnyxConnectionManager {
182
183
  });
183
184
  });
184
185
  this.connectionsMap.clear();
186
+ // Clear snapshot cache when all connections are disconnected
187
+ OnyxSnapshotCache_1.default.clear();
185
188
  }
186
189
  /**
187
190
  * Refreshes the connection manager's session ID.
188
191
  */
189
192
  refreshSessionID() {
190
193
  this.sessionID = Str.guid();
194
+ // Clear snapshot cache when session refreshes to avoid stale cache issues
195
+ OnyxSnapshotCache_1.default.clear();
191
196
  }
192
197
  /**
193
198
  * Adds the connection to the eviction block list. Connections added to this list can never be evicted.
@@ -0,0 +1,78 @@
1
+ import type { OnyxKey, OnyxValue } from './types';
2
+ import type { UseOnyxOptions, UseOnyxResult, UseOnyxSelector } from './useOnyx';
3
+ /**
4
+ * Manages snapshot caching for useOnyx hook performance optimization.
5
+ * Handles selector function tracking and memoized getSnapshot results.
6
+ */
7
+ declare class OnyxSnapshotCache {
8
+ /**
9
+ * Snapshot cache is a two-level map. The top-level keys are Onyx keys. The top-level values maps.
10
+ * The second-level keys are a custom composite string defined by this.registerConsumer. These represent a unique useOnyx config, which is not fully represented by the Onyx key alone.
11
+ * The reason we have two levels is for performance: not to make cache access faster, but to make cache invalidation faster.
12
+ * We can invalidate the snapshot cache for a given Onyx key with one map.delete operation on the top-level map, rather than having to loop through a large single-level map and delete any matching keys.
13
+ */
14
+ private snapshotCache;
15
+ /**
16
+ * Maps selector functions to unique IDs for cache key generation
17
+ */
18
+ private selectorIDMap;
19
+ /**
20
+ * Counter for generating incremental selector IDs
21
+ */
22
+ private selectorIDCounter;
23
+ /**
24
+ * Reference counting for cache keys to enable automatic cleanup.
25
+ * Maps cache key (string) to number of consumers using it.
26
+ */
27
+ private cacheKeyRefCounts;
28
+ constructor();
29
+ /**
30
+ * Generate unique ID for selector functions using incrementing numbers
31
+ */
32
+ getSelectorID<TKey extends OnyxKey, TReturnValue>(selector: UseOnyxSelector<TKey, TReturnValue>): number;
33
+ /**
34
+ * Register a consumer for a cache key and return the cache key.
35
+ * Generates cache key and increments reference counter.
36
+ *
37
+ * The properties used to generate the cache key are handpicked for performance reasons and
38
+ * according to their purpose and effect they produce in the useOnyx hook behavior:
39
+ *
40
+ * - `selector`: Different selectors produce different results, so each selector needs its own cache entry
41
+ * - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
42
+ * - `allowStaleData`: Controls whether stale data can be returned during pending merges, affecting result timing
43
+ * - `canBeMissing`: Determines logging behavior for missing data, but doesn't affect the actual data returned
44
+ *
45
+ * Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
46
+ * or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
47
+ */
48
+ registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues' | 'allowStaleData' | 'canBeMissing'>): string;
49
+ /**
50
+ * Deregister a consumer for a cache key.
51
+ * Decrements reference counter and removes cache entry if no consumers remain.
52
+ */
53
+ deregisterConsumer(key: OnyxKey, cacheKey: string): void;
54
+ /**
55
+ * Get cached snapshot result for a key and cache key combination
56
+ */
57
+ getCachedResult<TResult extends UseOnyxResult<OnyxValue<OnyxKey>>>(key: OnyxKey, cacheKey: string): TResult | undefined;
58
+ /**
59
+ * Set cached snapshot result for a key and cache key combination
60
+ */
61
+ setCachedResult<TResult extends UseOnyxResult<OnyxValue<OnyxKey>>>(key: OnyxKey, cacheKey: string, result: TResult): void;
62
+ /**
63
+ * Selective cache invalidation to prevent data unavailability
64
+ * Collection members invalidate upward, collections don't cascade downward
65
+ */
66
+ invalidateForKey(keyToInvalidate: OnyxKey): void;
67
+ /**
68
+ * Clear all snapshot cache
69
+ */
70
+ clear(): void;
71
+ /**
72
+ * Clear selector ID mappings (useful for testing)
73
+ */
74
+ clearSelectorIds(): void;
75
+ }
76
+ declare const onyxSnapshotCache: OnyxSnapshotCache;
77
+ export default onyxSnapshotCache;
78
+ export { OnyxSnapshotCache };
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OnyxSnapshotCache = void 0;
7
+ const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
8
+ /**
9
+ * Manages snapshot caching for useOnyx hook performance optimization.
10
+ * Handles selector function tracking and memoized getSnapshot results.
11
+ */
12
+ class OnyxSnapshotCache {
13
+ constructor() {
14
+ this.snapshotCache = new Map();
15
+ this.selectorIDMap = new Map();
16
+ this.selectorIDCounter = 0;
17
+ this.cacheKeyRefCounts = new Map();
18
+ }
19
+ /**
20
+ * Generate unique ID for selector functions using incrementing numbers
21
+ */
22
+ getSelectorID(selector) {
23
+ const typedSelector = selector;
24
+ if (!this.selectorIDMap.has(typedSelector)) {
25
+ const id = this.selectorIDCounter++;
26
+ this.selectorIDMap.set(typedSelector, id);
27
+ }
28
+ return this.selectorIDMap.get(typedSelector);
29
+ }
30
+ /**
31
+ * Register a consumer for a cache key and return the cache key.
32
+ * Generates cache key and increments reference counter.
33
+ *
34
+ * The properties used to generate the cache key are handpicked for performance reasons and
35
+ * according to their purpose and effect they produce in the useOnyx hook behavior:
36
+ *
37
+ * - `selector`: Different selectors produce different results, so each selector needs its own cache entry
38
+ * - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
39
+ * - `allowStaleData`: Controls whether stale data can be returned during pending merges, affecting result timing
40
+ * - `canBeMissing`: Determines logging behavior for missing data, but doesn't affect the actual data returned
41
+ *
42
+ * Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
43
+ * or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
44
+ */
45
+ registerConsumer(options) {
46
+ var _a, _b, _c;
47
+ const selectorID = (options === null || options === void 0 ? void 0 : options.selector) ? this.getSelectorID(options.selector) : 'no_selector';
48
+ // Create options hash without expensive JSON.stringify
49
+ const initWithStoredValues = (_a = options === null || options === void 0 ? void 0 : options.initWithStoredValues) !== null && _a !== void 0 ? _a : true;
50
+ const allowStaleData = (_b = options === null || options === void 0 ? void 0 : options.allowStaleData) !== null && _b !== void 0 ? _b : false;
51
+ const canBeMissing = (_c = options === null || options === void 0 ? void 0 : options.canBeMissing) !== null && _c !== void 0 ? _c : true;
52
+ const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}_${canBeMissing}`;
53
+ // Increment reference count for this cache key
54
+ const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
55
+ this.cacheKeyRefCounts.set(cacheKey, currentCount + 1);
56
+ return cacheKey;
57
+ }
58
+ /**
59
+ * Deregister a consumer for a cache key.
60
+ * Decrements reference counter and removes cache entry if no consumers remain.
61
+ */
62
+ deregisterConsumer(key, cacheKey) {
63
+ const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
64
+ if (currentCount <= 1) {
65
+ // Last consumer - remove from reference counter and cache
66
+ this.cacheKeyRefCounts.delete(cacheKey);
67
+ // Remove from snapshot cache
68
+ const keyCache = this.snapshotCache.get(key);
69
+ if (keyCache) {
70
+ keyCache.delete(cacheKey);
71
+ // If this was the last cache entry for this Onyx key, remove the key entirely
72
+ if (keyCache.size === 0) {
73
+ this.snapshotCache.delete(key);
74
+ }
75
+ }
76
+ }
77
+ else {
78
+ // Still has other consumers - just decrement count
79
+ this.cacheKeyRefCounts.set(cacheKey, currentCount - 1);
80
+ }
81
+ }
82
+ /**
83
+ * Get cached snapshot result for a key and cache key combination
84
+ */
85
+ getCachedResult(key, cacheKey) {
86
+ const keyCache = this.snapshotCache.get(key);
87
+ return keyCache === null || keyCache === void 0 ? void 0 : keyCache.get(cacheKey);
88
+ }
89
+ /**
90
+ * Set cached snapshot result for a key and cache key combination
91
+ */
92
+ setCachedResult(key, cacheKey, result) {
93
+ if (!this.snapshotCache.has(key)) {
94
+ this.snapshotCache.set(key, new Map());
95
+ }
96
+ this.snapshotCache.get(key).set(cacheKey, result);
97
+ }
98
+ /**
99
+ * Selective cache invalidation to prevent data unavailability
100
+ * Collection members invalidate upward, collections don't cascade downward
101
+ */
102
+ invalidateForKey(keyToInvalidate) {
103
+ // Always invalidate the exact key
104
+ this.snapshotCache.delete(keyToInvalidate);
105
+ try {
106
+ // Check if the key is a collection member and invalidate the collection base key
107
+ const collectionBaseKey = OnyxUtils_1.default.getCollectionKey(keyToInvalidate);
108
+ this.snapshotCache.delete(collectionBaseKey);
109
+ }
110
+ catch (e) {
111
+ // do nothing - this just means the key is not a collection member
112
+ }
113
+ }
114
+ /**
115
+ * Clear all snapshot cache
116
+ */
117
+ clear() {
118
+ this.snapshotCache.clear();
119
+ }
120
+ /**
121
+ * Clear selector ID mappings (useful for testing)
122
+ */
123
+ clearSelectorIds() {
124
+ this.selectorIDCounter = 0;
125
+ }
126
+ }
127
+ exports.OnyxSnapshotCache = OnyxSnapshotCache;
128
+ // Create and export a singleton instance
129
+ const onyxSnapshotCache = new OnyxSnapshotCache();
130
+ exports.default = onyxSnapshotCache;
@@ -143,14 +143,14 @@ declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionK
143
143
  /**
144
144
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
145
145
  */
146
- declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean): void;
146
+ declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean, notifyUseOnyxHookSubscribers?: boolean): void;
147
147
  /**
148
148
  * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
149
149
  *
150
150
  * @example
151
151
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
152
152
  */
153
- declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean): void;
153
+ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean, notifyUseOnyxHookSubscribers?: boolean): void;
154
154
  /**
155
155
  * Sends the data obtained from the keys to the connection. It either:
156
156
  * - sets state on the withOnyxInstances
package/dist/OnyxUtils.js CHANGED
@@ -512,7 +512,7 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
512
512
  /**
513
513
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
514
514
  */
515
- function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true) {
515
+ function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true, notifyUseOnyxHookSubscribers = true) {
516
516
  // We prepare the "cached collection" which is the entire collection + the new partial data that
517
517
  // was merged in via mergeCollection().
518
518
  const cachedCollection = getCachedCollection(collectionKey);
@@ -540,7 +540,8 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
540
540
  const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
541
541
  // Regular Onyx.connect() subscriber found.
542
542
  if (typeof subscriber.callback === 'function') {
543
- if (!notifyConnectSubscribers) {
543
+ // Check if it's a useOnyx or a regular Onyx.connect() subscriber
544
+ if ((subscriber.isUseOnyxSubscriber && !notifyUseOnyxHookSubscribers) || (!subscriber.isUseOnyxSubscriber && !notifyConnectSubscribers)) {
544
545
  continue;
545
546
  }
546
547
  // If they are subscribed to the collection key and using waitForCollectionCallback then we'll
@@ -670,7 +671,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
670
671
  * @example
671
672
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
672
673
  */
673
- function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true) {
674
+ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true, notifyUseOnyxHookSubscribers = true) {
674
675
  var _a, _b;
675
676
  // Add or remove this key from the recentlyAccessedKeys lists
676
677
  if (value !== null) {
@@ -709,7 +710,8 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
709
710
  }
710
711
  // Subscriber is a regular call to connect() and provided a callback
711
712
  if (typeof subscriber.callback === 'function') {
712
- if (!notifyConnectSubscribers) {
713
+ // Check if it's a useOnyx or a regular Onyx.connect() subscriber
714
+ if ((subscriber.isUseOnyxSubscriber && !notifyUseOnyxHookSubscribers) || (!subscriber.isUseOnyxSubscriber && !notifyConnectSubscribers)) {
713
715
  continue;
714
716
  }
715
717
  if (lastConnectionCallbackData.has(subscriber.subscriptionID) && lastConnectionCallbackData.get(subscriber.subscriptionID) === value) {
@@ -889,8 +891,8 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
889
891
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
890
892
  */
891
893
  function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber = () => true) {
892
- const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false));
893
- batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true));
894
+ const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false, false));
895
+ batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true, true));
894
896
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
895
897
  }
896
898
  /**
@@ -899,8 +901,8 @@ function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber
899
901
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
900
902
  */
901
903
  function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
902
- const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false));
903
- batchUpdates(() => keysChanged(key, value, previousValue, false, true));
904
+ const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false, false));
905
+ batchUpdates(() => keysChanged(key, value, previousValue, false, true, true));
904
906
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
905
907
  }
906
908
  /**
package/dist/types.d.ts CHANGED
@@ -235,6 +235,8 @@ type BaseConnectOptions = {
235
235
  * with the same connect configurations.
236
236
  */
237
237
  reuseConnection?: boolean;
238
+ /** Indicates whether this subscriber is created from the useOnyx hook. */
239
+ isUseOnyxSubscriber?: boolean;
238
240
  };
239
241
  /** Represents the callback function used in `Onyx.connect()` method with a regular key. */
240
242
  type DefaultConnectCallback<TKey extends OnyxKey> = (value: OnyxEntry<KeyValueMapping[TKey]>, key: TKey) => void;
package/dist/useOnyx.d.ts CHANGED
@@ -47,4 +47,4 @@ type ResultMetadata<TValue> = {
47
47
  type UseOnyxResult<TValue> = [NonNullable<TValue> | undefined, ResultMetadata<TValue>];
48
48
  declare function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey, options?: UseOnyxOptions<TKey, TReturnValue>, dependencies?: DependencyList): UseOnyxResult<TReturnValue>;
49
49
  export default useOnyx;
50
- export type { FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions };
50
+ export type { FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions, UseOnyxSelector };
package/dist/useOnyx.js CHANGED
@@ -45,6 +45,7 @@ const GlobalSettings = __importStar(require("./GlobalSettings"));
45
45
  const usePrevious_1 = __importDefault(require("./usePrevious"));
46
46
  const metrics_1 = __importDefault(require("./metrics"));
47
47
  const Logger = __importStar(require("./Logger"));
48
+ const OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache"));
48
49
  const useLiveRef_1 = __importDefault(require("./useLiveRef"));
49
50
  function useOnyx(key, options, dependencies = []) {
50
51
  const connectionRef = (0, react_1.useRef)(null);
@@ -105,6 +106,14 @@ function useOnyx(key, options, dependencies = []) {
105
106
  const shouldGetCachedValueRef = (0, react_1.useRef)(true);
106
107
  // Inside useOnyx.ts, we need to track the sourceValue separately
107
108
  const sourceValueRef = (0, react_1.useRef)(undefined);
109
+ // Cache the options key to avoid regenerating it every getSnapshot call
110
+ const cacheKey = (0, react_1.useMemo)(() => OnyxSnapshotCache_1.default.registerConsumer({
111
+ selector: options === null || options === void 0 ? void 0 : options.selector,
112
+ initWithStoredValues: options === null || options === void 0 ? void 0 : options.initWithStoredValues,
113
+ allowStaleData: options === null || options === void 0 ? void 0 : options.allowStaleData,
114
+ canBeMissing: options === null || options === void 0 ? void 0 : options.canBeMissing,
115
+ }), [options === null || options === void 0 ? void 0 : options.selector, options === null || options === void 0 ? void 0 : options.initWithStoredValues, options === null || options === void 0 ? void 0 : options.allowStaleData, options === null || options === void 0 ? void 0 : options.canBeMissing]);
116
+ (0, react_1.useEffect)(() => () => OnyxSnapshotCache_1.default.deregisterConsumer(key, cacheKey), [key, cacheKey]);
108
117
  (0, react_1.useEffect)(() => {
109
118
  // These conditions will ensure we can only handle dynamic collection member keys from the same collection.
110
119
  if ((options === null || options === void 0 ? void 0 : options.allowDynamicKey) || previousKey === key) {
@@ -137,6 +146,8 @@ function useOnyx(key, options, dependencies = []) {
137
146
  if (connectionRef.current === null || isConnectingRef.current || !onStoreChangeFnRef.current) {
138
147
  return;
139
148
  }
149
+ // Invalidate cache when dependencies change so selector runs with new closure values
150
+ OnyxSnapshotCache_1.default.invalidateForKey(key);
140
151
  shouldGetCachedValueRef.current = true;
141
152
  onStoreChangeFnRef.current();
142
153
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -158,10 +169,23 @@ function useOnyx(key, options, dependencies = []) {
158
169
  }, [key, options === null || options === void 0 ? void 0 : options.canEvict]);
159
170
  const getSnapshot = (0, react_1.useCallback)(() => {
160
171
  var _a, _b, _c, _d;
172
+ // Check if we have any cache for this Onyx key
173
+ // Don't use cache for first connection with initWithStoredValues: false
174
+ // Also don't use cache during active data updates (when shouldGetCachedValueRef is true)
175
+ if (!(isFirstConnectionRef.current && (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false) && !shouldGetCachedValueRef.current) {
176
+ const cachedResult = OnyxSnapshotCache_1.default.getCachedResult(key, cacheKey);
177
+ if (cachedResult !== undefined) {
178
+ resultRef.current = cachedResult;
179
+ return cachedResult;
180
+ }
181
+ }
161
182
  let isOnyxValueDefined = true;
162
183
  // We return the initial result right away during the first connection if `initWithStoredValues` is set to `false`.
163
184
  if (isFirstConnectionRef.current && (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false) {
164
- return resultRef.current;
185
+ const result = resultRef.current;
186
+ // Store result in snapshot cache
187
+ OnyxSnapshotCache_1.default.setCachedResult(key, cacheKey, result);
188
+ return result;
165
189
  }
166
190
  // We get the value from cache while the first connection to Onyx is being made,
167
191
  // so we can return any cached value right away. After the connection is made, we only
@@ -188,7 +212,7 @@ function useOnyx(key, options, dependencies = []) {
188
212
  newValueRef.current = undefined;
189
213
  newFetchStatus = 'loading';
190
214
  }
191
- // Optimized equality checking - eliminated redundant deep equality:
215
+ // Optimized equality checking:
192
216
  // - Memoized selectors already handle deep equality internally, so we can use fast reference equality
193
217
  // - Non-selector cases use shallow equality for object reference checks
194
218
  // - Normalize null to undefined to ensure consistent comparison (both represent "no value")
@@ -226,8 +250,9 @@ function useOnyx(key, options, dependencies = []) {
226
250
  Logger.logAlert(`useOnyx returned no data for key with canBeMissing set to false for key ${key}`, { showAlert: true });
227
251
  }
228
252
  }
253
+ OnyxSnapshotCache_1.default.setCachedResult(key, cacheKey, resultRef.current);
229
254
  return resultRef.current;
230
- }, [options === null || options === void 0 ? void 0 : options.initWithStoredValues, options === null || options === void 0 ? void 0 : options.allowStaleData, options === null || options === void 0 ? void 0 : options.canBeMissing, key, memoizedSelector]);
255
+ }, [options === null || options === void 0 ? void 0 : options.initWithStoredValues, options === null || options === void 0 ? void 0 : options.allowStaleData, options === null || options === void 0 ? void 0 : options.canBeMissing, key, memoizedSelector, cacheKey]);
231
256
  const subscribe = (0, react_1.useCallback)((onStoreChange) => {
232
257
  isConnectingRef.current = true;
233
258
  onStoreChangeFnRef.current = onStoreChange;
@@ -243,12 +268,15 @@ function useOnyx(key, options, dependencies = []) {
243
268
  shouldGetCachedValueRef.current = true;
244
269
  // sourceValue is unknown type, so we need to cast it to the correct type.
245
270
  sourceValueRef.current = sourceValue;
271
+ // Invalidate snapshot cache for this key when data changes
272
+ OnyxSnapshotCache_1.default.invalidateForKey(key);
246
273
  // Finally, we signal that the store changed, making `getSnapshot()` be called again.
247
274
  onStoreChange();
248
275
  },
249
276
  initWithStoredValues: options === null || options === void 0 ? void 0 : options.initWithStoredValues,
250
277
  waitForCollectionCallback: OnyxUtils_1.default.isCollectionKey(key),
251
278
  reuseConnection: options === null || options === void 0 ? void 0 : options.reuseConnection,
279
+ isUseOnyxSubscriber: true,
252
280
  });
253
281
  checkEvictableKey();
254
282
  return () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.134",
3
+ "version": "2.0.136",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",