react-native-onyx 2.0.42 → 2.0.43

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/Onyx.js CHANGED
@@ -47,7 +47,7 @@ function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCach
47
47
  storage_1.default.init();
48
48
  if (shouldSyncMultipleInstances) {
49
49
  (_a = storage_1.default.keepInstancesSync) === null || _a === void 0 ? void 0 : _a.call(storage_1.default, (key, value) => {
50
- const prevValue = OnyxCache_1.default.getValue(key, false);
50
+ const prevValue = OnyxCache_1.default.get(key, false);
51
51
  OnyxCache_1.default.set(key, value);
52
52
  OnyxUtils_1.default.keyChanged(key, value, prevValue);
53
53
  });
@@ -106,7 +106,7 @@ function connect(connectOptions) {
106
106
  // Performance improvement
107
107
  // If the mapping is connected to an onyx key that is not a collection
108
108
  // we can skip the call to getAllKeys() and return an array with a single item
109
- if (Boolean(mapping.key) && typeof mapping.key === 'string' && !mapping.key.endsWith('_') && OnyxCache_1.default.storageKeys.has(mapping.key)) {
109
+ if (Boolean(mapping.key) && typeof mapping.key === 'string' && !mapping.key.endsWith('_') && OnyxCache_1.default.getAllKeys().has(mapping.key)) {
110
110
  return new Set([mapping.key]);
111
111
  }
112
112
  return OnyxUtils_1.default.getAllKeys();
@@ -122,9 +122,9 @@ function connect(connectOptions) {
122
122
  // component. This null value will be filtered out so that the connected component can utilize defaultProps.
123
123
  if (matchingKeys.length === 0) {
124
124
  if (mapping.key && !OnyxUtils_1.default.isCollectionKey(mapping.key)) {
125
- OnyxCache_1.default.set(mapping.key, null);
125
+ OnyxCache_1.default.addNullishStorageKey(mapping.key);
126
126
  }
127
- // Here we cannot use batching because the null value is expected to be set immediately for default props
127
+ // Here we cannot use batching because the nullish value is expected to be set immediately for default props
128
128
  // or they will be undefined.
129
129
  OnyxUtils_1.default.sendDataToConnection(mapping, null, undefined, false);
130
130
  return;
@@ -192,8 +192,17 @@ function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
192
192
  * @param value value to store
193
193
  */
194
194
  function set(key, value) {
195
- // check if the value is compatible with the existing value in the storage
196
- const existingValue = OnyxCache_1.default.getValue(key, false);
195
+ // When we use Onyx.set to set a key we want to clear the current delta changes from Onyx.merge that were queued
196
+ // before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
197
+ if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
198
+ delete OnyxUtils_1.default.getMergeQueue()[key];
199
+ }
200
+ const existingValue = OnyxCache_1.default.get(key, false);
201
+ // If the existing value as well as the new value are null, we can return early.
202
+ if (value === null && existingValue === null) {
203
+ return Promise.resolve();
204
+ }
205
+ // Check if the value is compatible with the existing value in the storage
197
206
  const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(value, existingValue);
198
207
  if (!isCompatible) {
199
208
  Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'set', existingValueType, newValueType));
@@ -201,17 +210,23 @@ function set(key, value) {
201
210
  }
202
211
  // If the value is null, we remove the key from storage
203
212
  const { value: valueAfterRemoving, wasRemoved } = OnyxUtils_1.default.removeNullValues(key, value);
204
- const valueWithoutNullValues = valueAfterRemoving;
205
- if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
206
- delete OnyxUtils_1.default.getMergeQueue()[key];
213
+ const logSetCall = (hasChanged = true) => {
214
+ // Logging properties only since values could be sensitive things we don't want to log
215
+ Logger.logInfo(`set called for key: ${key}${underscore_1.default.isObject(value) ? ` properties: ${underscore_1.default.keys(value).join(',')}` : ''} hasChanged: ${hasChanged}`);
216
+ };
217
+ // Calling "OnyxUtils.removeNullValues" removes the key from storage and cache and updates the subscriber.
218
+ // Therefore, we don't need to further broadcast and update the value so we can return early.
219
+ if (wasRemoved) {
220
+ logSetCall();
221
+ return Promise.resolve();
207
222
  }
223
+ const valueWithoutNullValues = valueAfterRemoving;
208
224
  const hasChanged = OnyxCache_1.default.hasValueChanged(key, valueWithoutNullValues);
209
- // Logging properties only since values could be sensitive things we don't want to log
210
- Logger.logInfo(`set called for key: ${key}${underscore_1.default.isObject(value) ? ` properties: ${underscore_1.default.keys(value).join(',')}` : ''} hasChanged: ${hasChanged}`);
225
+ logSetCall(hasChanged);
211
226
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
212
- const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueWithoutNullValues, hasChanged, wasRemoved);
227
+ const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueWithoutNullValues, hasChanged);
213
228
  // If the value has not changed or the key got removed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
214
- if (!hasChanged || wasRemoved) {
229
+ if (!hasChanged) {
215
230
  return updatePromise;
216
231
  }
217
232
  return storage_1.default.setItem(key, valueWithoutNullValues)
@@ -229,18 +244,28 @@ function set(key, value) {
229
244
  * @param data object keyed by ONYXKEYS and the values to set
230
245
  */
231
246
  function multiSet(data) {
232
- const keyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(data, true);
233
- const updatePromises = keyValuePairs.map(([key, value]) => {
234
- const prevValue = OnyxCache_1.default.getValue(key, false);
247
+ const allKeyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(data, true);
248
+ // When a key is set to null, we need to remove the remove the key from storage using "OnyxUtils.remove".
249
+ // Therefore, we filter the key value pairs to exclude null values and remove those keys explicitly.
250
+ const removePromises = [];
251
+ const keyValuePairsToUpdate = allKeyValuePairs.filter(([key, value]) => {
252
+ if (value === null) {
253
+ removePromises.push(OnyxUtils_1.default.remove(key));
254
+ return false;
255
+ }
256
+ return true;
257
+ });
258
+ const updatePromises = keyValuePairsToUpdate.map(([key, value]) => {
259
+ const prevValue = OnyxCache_1.default.get(key, false);
235
260
  // Update cache and optimistically inform subscribers on the next tick
236
261
  OnyxCache_1.default.set(key, value);
237
262
  return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, prevValue);
238
263
  });
239
- return storage_1.default.multiSet(keyValuePairs)
264
+ return storage_1.default.multiSet(allKeyValuePairs)
240
265
  .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSet, data))
241
266
  .then(() => {
242
267
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MULTI_SET, undefined, data);
243
- return Promise.all(updatePromises);
268
+ return Promise.all([removePromises, updatePromises]);
244
269
  })
245
270
  .then(() => undefined);
246
271
  }
@@ -278,7 +303,7 @@ function merge(key, changes) {
278
303
  mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((existingValue) => {
279
304
  // Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
280
305
  if (mergeQueue[key] == null) {
281
- return undefined;
306
+ return Promise.resolve();
282
307
  }
283
308
  try {
284
309
  // We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
@@ -291,7 +316,7 @@ function merge(key, changes) {
291
316
  return isCompatible;
292
317
  });
293
318
  if (!validChanges.length) {
294
- return undefined;
319
+ return Promise.resolve();
295
320
  }
296
321
  const batchedDeltaChanges = OnyxUtils_1.default.applyMerge(undefined, validChanges, false);
297
322
  // Case (1): When there is no existing value in storage, we want to set the value instead of merge it.
@@ -301,8 +326,18 @@ function merge(key, changes) {
301
326
  // Clean up the write queue, so we don't apply these changes again
302
327
  delete mergeQueue[key];
303
328
  delete mergeQueuePromise[key];
329
+ const logMergeCall = (hasChanged = true) => {
330
+ // Logging properties only since values could be sensitive things we don't want to log
331
+ Logger.logInfo(`merge called for key: ${key}${underscore_1.default.isObject(batchedDeltaChanges) ? ` properties: ${underscore_1.default.keys(batchedDeltaChanges).join(',')}` : ''} hasChanged: ${hasChanged}`);
332
+ };
304
333
  // If the batched changes equal null, we want to remove the key from storage, to reduce storage size
305
334
  const { wasRemoved } = OnyxUtils_1.default.removeNullValues(key, batchedDeltaChanges);
335
+ // Calling "OnyxUtils.removeNullValues" removes the key from storage and cache and updates the subscriber.
336
+ // Therefore, we don't need to further broadcast and update the value so we can return early.
337
+ if (wasRemoved) {
338
+ logMergeCall();
339
+ return Promise.resolve();
340
+ }
306
341
  // For providers that can't handle delta changes, we need to merge the batched changes with the existing value beforehand.
307
342
  // The "preMergedValue" will be directly "set" in storage instead of being merged
308
343
  // Therefore we merge the batched changes with the existing value to get the final merged value that will be stored.
@@ -310,12 +345,11 @@ function merge(key, changes) {
310
345
  const preMergedValue = OnyxUtils_1.default.applyMerge(shouldSetValue ? undefined : existingValue, [batchedDeltaChanges], true);
311
346
  // In cache, we don't want to remove the key if it's null to improve performance and speed up the next merge.
312
347
  const hasChanged = OnyxCache_1.default.hasValueChanged(key, preMergedValue);
313
- // Logging properties only since values could be sensitive things we don't want to log
314
- Logger.logInfo(`merge called for key: ${key}${underscore_1.default.isObject(batchedDeltaChanges) ? ` properties: ${underscore_1.default.keys(batchedDeltaChanges).join(',')}` : ''} hasChanged: ${hasChanged}`);
348
+ logMergeCall(hasChanged);
315
349
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
316
- const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, preMergedValue, hasChanged, wasRemoved);
350
+ const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, preMergedValue, hasChanged);
317
351
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
318
- if (!hasChanged || wasRemoved) {
352
+ if (!hasChanged) {
319
353
  return updatePromise;
320
354
  }
321
355
  return storage_1.default.mergeItem(key, batchedDeltaChanges, preMergedValue, shouldSetValue).then(() => {
@@ -453,6 +487,7 @@ function mergeCollection(collectionKey, collection) {
453
487
  function clear(keysToPreserve = []) {
454
488
  return OnyxUtils_1.default.getAllKeys()
455
489
  .then((keys) => {
490
+ OnyxCache_1.default.clearNullishStorageKeys();
456
491
  const keysToBeClearedFromStorage = [];
457
492
  const keyValuesToResetAsCollection = {};
458
493
  const keyValuesToResetIndividually = {};
@@ -471,7 +506,7 @@ function clear(keysToPreserve = []) {
471
506
  // 2. Figure out whether it is a collection key or not,
472
507
  // since collection key subscribers need to be updated differently
473
508
  if (!isKeyToPreserve) {
474
- const oldValue = OnyxCache_1.default.getValue(key);
509
+ const oldValue = OnyxCache_1.default.get(key);
475
510
  const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : null;
476
511
  if (newValue !== oldValue) {
477
512
  OnyxCache_1.default.set(key, newValue);
@@ -496,7 +531,7 @@ function clear(keysToPreserve = []) {
496
531
  const updatePromises = [];
497
532
  // Notify the subscribers for each key/value group so they can receive the new values
498
533
  Object.entries(keyValuesToResetIndividually).forEach(([key, value]) => {
499
- updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, OnyxCache_1.default.getValue(key, false)));
534
+ updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, OnyxCache_1.default.get(key, false)));
500
535
  });
501
536
  Object.entries(keyValuesToResetAsCollection).forEach(([key, value]) => {
502
537
  updatePromises.push(OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(key, value));
@@ -5,7 +5,9 @@ import type { OnyxKey, OnyxValue } from './types';
5
5
  */
6
6
  declare class OnyxCache {
7
7
  /** Cache of all the storage keys available in persistent storage */
8
- storageKeys: Set<OnyxKey>;
8
+ private storageKeys;
9
+ /** A list of keys where a nullish value has been fetched from storage before, but the key still exists in cache */
10
+ private nullishStorageKeys;
9
11
  /** Unique list of keys maintained in access order (most recent at the end) */
10
12
  private recentKeys;
11
13
  /** A map of cached values */
@@ -21,16 +23,32 @@ declare class OnyxCache {
21
23
  /** Get all the storage keys */
22
24
  getAllKeys(): Set<OnyxKey>;
23
25
  /**
24
- * Get a cached value from storage
25
- * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
26
+ * Allows to set all the keys at once.
27
+ * This is useful when we are getting
28
+ * all the keys from the storage provider
29
+ * and we want to keep the cache in sync.
30
+ *
31
+ * Previously, we had to call `addKey` in a loop
32
+ * to achieve the same result.
33
+ *
34
+ * @param keys - an array of keys
26
35
  */
27
- getValue(key: OnyxKey, shouldReindexCache?: boolean): OnyxValue<OnyxKey>;
28
- /** Check whether cache has data for the given key */
29
- hasCacheForKey(key: OnyxKey): boolean;
36
+ setAllKeys(keys: OnyxKey[]): void;
30
37
  /** Saves a key in the storage keys list
31
38
  * Serves to keep the result of `getAllKeys` up to date
32
39
  */
33
40
  addKey(key: OnyxKey): void;
41
+ /** Used to set keys that are null/undefined in storage without adding null to the storage map */
42
+ addNullishStorageKey(key: OnyxKey): void;
43
+ /** Used to clear keys that are null/undefined in cache */
44
+ clearNullishStorageKeys(): void;
45
+ /** Check whether cache has data for the given key */
46
+ hasCacheForKey(key: OnyxKey): boolean;
47
+ /**
48
+ * Get a cached value from storage
49
+ * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
50
+ */
51
+ get(key: OnyxKey, shouldReindexCache?: boolean): OnyxValue<OnyxKey>;
34
52
  /**
35
53
  * Set's a key value in cache
36
54
  * Adds the key to the storage keys list as well
@@ -43,18 +61,6 @@ declare class OnyxCache {
43
61
  * @param data - a map of (cache) key - values
44
62
  */
45
63
  merge(data: Record<OnyxKey, OnyxValue<OnyxKey>>): void;
46
- /**
47
- * Allows to set all the keys at once.
48
- * This is useful when we are getting
49
- * all the keys from the storage provider
50
- * and we want to keep the cache in sync.
51
- *
52
- * Previously, we had to call `addKey` in a loop
53
- * to achieve the same result.
54
- *
55
- * @param keys - an array of keys
56
- */
57
- setAllKeys(keys: OnyxKey[]): void;
58
64
  /**
59
65
  * Check whether the given task is already running
60
66
  * @param taskName - unique name given for the task
package/dist/OnyxCache.js CHANGED
@@ -15,29 +15,30 @@ class OnyxCache {
15
15
  /** Maximum size of the keys store din cache */
16
16
  this.maxRecentKeysSize = 0;
17
17
  this.storageKeys = new Set();
18
+ this.nullishStorageKeys = new Set();
18
19
  this.recentKeys = new Set();
19
20
  this.storageMap = {};
20
21
  this.pendingPromises = new Map();
21
22
  // bind all public methods to prevent problems with `this`
22
- (0, bindAll_1.default)(this, 'getAllKeys', 'getValue', 'hasCacheForKey', 'addKey', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys');
23
+ (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys');
23
24
  }
24
25
  /** Get all the storage keys */
25
26
  getAllKeys() {
26
27
  return this.storageKeys;
27
28
  }
28
29
  /**
29
- * Get a cached value from storage
30
- * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
30
+ * Allows to set all the keys at once.
31
+ * This is useful when we are getting
32
+ * all the keys from the storage provider
33
+ * and we want to keep the cache in sync.
34
+ *
35
+ * Previously, we had to call `addKey` in a loop
36
+ * to achieve the same result.
37
+ *
38
+ * @param keys - an array of keys
31
39
  */
32
- getValue(key, shouldReindexCache = true) {
33
- if (shouldReindexCache) {
34
- this.addToAccessedKeys(key);
35
- }
36
- return this.storageMap[key];
37
- }
38
- /** Check whether cache has data for the given key */
39
- hasCacheForKey(key) {
40
- return this.storageMap[key] !== undefined;
40
+ setAllKeys(keys) {
41
+ this.storageKeys = new Set(keys);
41
42
  }
42
43
  /** Saves a key in the storage keys list
43
44
  * Serves to keep the result of `getAllKeys` up to date
@@ -45,6 +46,28 @@ class OnyxCache {
45
46
  addKey(key) {
46
47
  this.storageKeys.add(key);
47
48
  }
49
+ /** Used to set keys that are null/undefined in storage without adding null to the storage map */
50
+ addNullishStorageKey(key) {
51
+ this.nullishStorageKeys.add(key);
52
+ }
53
+ /** Used to clear keys that are null/undefined in cache */
54
+ clearNullishStorageKeys() {
55
+ this.nullishStorageKeys = new Set();
56
+ }
57
+ /** Check whether cache has data for the given key */
58
+ hasCacheForKey(key) {
59
+ return this.storageMap[key] !== undefined || this.nullishStorageKeys.has(key);
60
+ }
61
+ /**
62
+ * Get a cached value from storage
63
+ * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
64
+ */
65
+ get(key, shouldReindexCache = true) {
66
+ if (shouldReindexCache) {
67
+ this.addToAccessedKeys(key);
68
+ }
69
+ return this.storageMap[key];
70
+ }
48
71
  /**
49
72
  * Set's a key value in cache
50
73
  * Adds the key to the storage keys list as well
@@ -52,6 +75,13 @@ class OnyxCache {
52
75
  set(key, value) {
53
76
  this.addKey(key);
54
77
  this.addToAccessedKeys(key);
78
+ // When a key is explicitly set in cache, we can remove it from the list of nullish keys,
79
+ // since it will either be set to a non nullish value or removed from the cache completely.
80
+ this.nullishStorageKeys.delete(key);
81
+ if (value === null || value === undefined) {
82
+ delete this.storageMap[key];
83
+ return undefined;
84
+ }
55
85
  this.storageMap[key] = value;
56
86
  return value;
57
87
  }
@@ -69,25 +99,17 @@ class OnyxCache {
69
99
  if (typeof data !== 'object' || Array.isArray(data)) {
70
100
  throw new Error('data passed to cache.merge() must be an Object of onyx key/value pairs');
71
101
  }
72
- this.storageMap = Object.assign({}, utils_1.default.fastMerge(this.storageMap, data, false));
73
- const storageKeys = this.getAllKeys();
74
- const mergedKeys = Object.keys(data);
75
- this.storageKeys = new Set([...storageKeys, ...mergedKeys]);
76
- mergedKeys.forEach((key) => this.addToAccessedKeys(key));
77
- }
78
- /**
79
- * Allows to set all the keys at once.
80
- * This is useful when we are getting
81
- * all the keys from the storage provider
82
- * and we want to keep the cache in sync.
83
- *
84
- * Previously, we had to call `addKey` in a loop
85
- * to achieve the same result.
86
- *
87
- * @param keys - an array of keys
88
- */
89
- setAllKeys(keys) {
90
- this.storageKeys = new Set(keys);
102
+ this.storageMap = Object.assign({}, utils_1.default.fastMerge(this.storageMap, data));
103
+ Object.entries(data).forEach(([key, value]) => {
104
+ this.addKey(key);
105
+ this.addToAccessedKeys(key);
106
+ if (value === null || value === undefined) {
107
+ this.addNullishStorageKey(key);
108
+ }
109
+ else {
110
+ this.nullishStorageKeys.delete(key);
111
+ }
112
+ });
91
113
  }
92
114
  /**
93
115
  * Check whether the given task is already running
@@ -107,7 +107,7 @@ declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionK
107
107
  /**
108
108
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
109
109
  */
110
- declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, previousPartialCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
110
+ declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
111
111
  /**
112
112
  * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
113
113
  *
@@ -120,7 +120,7 @@ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TK
120
120
  * - sets state on the withOnyxInstances
121
121
  * - triggers the callback function
122
122
  */
123
- declare function sendDataToConnection<TKey extends OnyxKey>(mapping: Mapping<TKey>, val: OnyxValue<TKey>, matchedKey: TKey | undefined, isBatched: boolean): void;
123
+ declare function sendDataToConnection<TKey extends OnyxKey>(mapping: Mapping<TKey>, value: OnyxValue<TKey> | null, matchedKey: TKey | undefined, isBatched: boolean): void;
124
124
  /**
125
125
  * We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
126
126
  * run out of storage the least recently accessed key can be removed.
@@ -157,10 +157,10 @@ declare function evictStorageAndRetry<TMethod extends typeof Onyx.set | typeof O
157
157
  /**
158
158
  * Notifies subscribers and writes current value to cache
159
159
  */
160
- declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean, wasRemoved?: boolean): Promise<void>;
160
+ declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean): Promise<void>;
161
161
  declare function hasPendingMergeForKey(key: OnyxKey): boolean;
162
- type RemoveNullValuesOutput = {
163
- value: Record<string, unknown> | unknown[] | null;
162
+ type RemoveNullValuesOutput<Value extends OnyxValue<OnyxKey> | null> = {
163
+ value: Value | null;
164
164
  wasRemoved: boolean;
165
165
  };
166
166
  /**
@@ -170,7 +170,7 @@ type RemoveNullValuesOutput = {
170
170
  *
171
171
  * @returns The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
172
172
  */
173
- declare function removeNullValues(key: OnyxKey, value: OnyxValue<OnyxKey>, shouldRemoveNestedNulls?: boolean): RemoveNullValuesOutput;
173
+ declare function removeNullValues<Value extends OnyxValue<OnyxKey>>(key: OnyxKey, value: Value | null, shouldRemoveNestedNulls?: boolean): RemoveNullValuesOutput<Value>;
174
174
  /**
175
175
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
176
176
  * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
package/dist/OnyxUtils.js CHANGED
@@ -163,7 +163,7 @@ function reduceCollectionWithSelector(collection, selector, withOnyxInstanceStat
163
163
  function get(key) {
164
164
  // When we already have the value in cache - resolve right away
165
165
  if (OnyxCache_1.default.hasCacheForKey(key)) {
166
- return Promise.resolve(OnyxCache_1.default.getValue(key));
166
+ return Promise.resolve(OnyxCache_1.default.get(key));
167
167
  }
168
168
  const taskName = `get:${key}`;
169
169
  // When a value retrieving task for this key is still running hook to it
@@ -173,6 +173,10 @@ function get(key) {
173
173
  // Otherwise retrieve the value from storage and capture a promise to aid concurrent usages
174
174
  const promise = storage_1.default.getItem(key)
175
175
  .then((val) => {
176
+ if (val === undefined) {
177
+ OnyxCache_1.default.addNullishStorageKey(key);
178
+ return undefined;
179
+ }
176
180
  OnyxCache_1.default.set(key, val);
177
181
  return val;
178
182
  })
@@ -182,9 +186,9 @@ function get(key) {
182
186
  /** Returns current key names stored in persisted storage */
183
187
  function getAllKeys() {
184
188
  // When we've already read stored keys, resolve right away
185
- const storedKeys = OnyxCache_1.default.getAllKeys();
186
- if (storedKeys.size > 0) {
187
- return Promise.resolve(storedKeys);
189
+ const cachedKeys = OnyxCache_1.default.getAllKeys();
190
+ if (cachedKeys.size > 0) {
191
+ return Promise.resolve(cachedKeys);
188
192
  }
189
193
  const taskName = 'getAllKeys';
190
194
  // When a value retrieving task for all keys is still running hook to it
@@ -237,7 +241,7 @@ function isSafeEvictionKey(testKey) {
237
241
  * If the requested key is a collection, it will return an object with all the collection members.
238
242
  */
239
243
  function tryGetCachedValue(key, mapping) {
240
- let val = OnyxCache_1.default.getValue(key);
244
+ let val = OnyxCache_1.default.get(key);
241
245
  if (isCollectionKey(key)) {
242
246
  const allCacheKeys = OnyxCache_1.default.getAllKeys();
243
247
  // It is possible we haven't loaded all keys yet so we do not know if the
@@ -247,7 +251,7 @@ function tryGetCachedValue(key, mapping) {
247
251
  }
248
252
  const matchingKeys = Array.from(allCacheKeys).filter((k) => k.startsWith(key));
249
253
  const values = matchingKeys.reduce((finalObject, matchedKey) => {
250
- const cachedValue = OnyxCache_1.default.getValue(matchedKey);
254
+ const cachedValue = OnyxCache_1.default.get(matchedKey);
251
255
  if (cachedValue) {
252
256
  // This is permissible because we're in the process of constructing the final object in a reduce function.
253
257
  // eslint-disable-next-line no-param-reassign
@@ -334,23 +338,22 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
334
338
  if (!collectionMemberKeys && !isCollectionMemberKey(collectionKey, key)) {
335
339
  return;
336
340
  }
337
- collection[key] = OnyxCache_1.default.getValue(key);
341
+ collection[key] = OnyxCache_1.default.get(key);
338
342
  });
339
343
  return collection;
340
344
  }
341
345
  /**
342
346
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
343
347
  */
344
- function keysChanged(collectionKey, partialCollection, previousPartialCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
345
- const previousCollectionWithoutNestedNulls = previousPartialCollection === undefined ? {} : utils_1.default.removeNestedNullValues(previousPartialCollection);
348
+ function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
346
349
  // We prepare the "cached collection" which is the entire collection + the new partial data that
347
350
  // was merged in via mergeCollection().
348
351
  const cachedCollection = getCachedCollection(collectionKey);
349
- const cachedCollectionWithoutNestedNulls = utils_1.default.removeNestedNullValues(cachedCollection);
350
352
  // If the previous collection equals the new collection then we do not need to notify any subscribers.
351
- if (previousPartialCollection !== undefined && (0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls, previousCollectionWithoutNestedNulls)) {
353
+ if (partialPreviousCollection !== undefined && (0, fast_equals_1.deepEqual)(cachedCollection, partialPreviousCollection)) {
352
354
  return;
353
355
  }
356
+ const previousCollection = partialPreviousCollection !== null && partialPreviousCollection !== void 0 ? partialPreviousCollection : {};
354
357
  // We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or
355
358
  // individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection
356
359
  // and does not represent all of the combined keys and values for a collection key. It is just the "new" data that was merged in via mergeCollection().
@@ -381,7 +384,7 @@ function keysChanged(collectionKey, partialCollection, previousPartialCollection
381
384
  // send the whole cached collection.
382
385
  if (isSubscribedToCollectionKey) {
383
386
  if (subscriber.waitForCollectionCallback) {
384
- subscriber.callback(cachedCollectionWithoutNestedNulls);
387
+ subscriber.callback(cachedCollection);
385
388
  continue;
386
389
  }
387
390
  // If they are not using waitForCollectionCallback then we notify the subscriber with
@@ -389,21 +392,21 @@ function keysChanged(collectionKey, partialCollection, previousPartialCollection
389
392
  const dataKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
390
393
  for (let j = 0; j < dataKeys.length; j++) {
391
394
  const dataKey = dataKeys[j];
392
- if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[dataKey], previousCollectionWithoutNestedNulls[dataKey])) {
395
+ if ((0, fast_equals_1.deepEqual)(cachedCollection[dataKey], previousCollection[dataKey])) {
393
396
  continue;
394
397
  }
395
- subscriber.callback(cachedCollectionWithoutNestedNulls[dataKey], dataKey);
398
+ subscriber.callback(cachedCollection[dataKey], dataKey);
396
399
  }
397
400
  continue;
398
401
  }
399
402
  // And if the subscriber is specifically only tracking a particular collection member key then we will
400
403
  // notify them with the cached data for that key only.
401
404
  if (isSubscribedToCollectionMemberKey) {
402
- if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[subscriber.key], previousCollectionWithoutNestedNulls[subscriber.key])) {
405
+ if ((0, fast_equals_1.deepEqual)(cachedCollection[subscriber.key], previousCollection[subscriber.key])) {
403
406
  continue;
404
407
  }
405
408
  const subscriberCallback = subscriber.callback;
406
- subscriberCallback(cachedCollectionWithoutNestedNulls[subscriber.key], subscriber.key);
409
+ subscriberCallback(cachedCollection[subscriber.key], subscriber.key);
407
410
  continue;
408
411
  }
409
412
  continue;
@@ -449,7 +452,7 @@ function keysChanged(collectionKey, partialCollection, previousPartialCollection
449
452
  }
450
453
  // If a React component is only interested in a single key then we can set the cached value directly to the state name.
451
454
  if (isSubscribedToCollectionMemberKey) {
452
- if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[subscriber.key], previousCollectionWithoutNestedNulls[subscriber.key])) {
455
+ if ((0, fast_equals_1.deepEqual)(cachedCollection[subscriber.key], previousCollection[subscriber.key])) {
453
456
  continue;
454
457
  }
455
458
  // However, we only want to update this subscriber if the partial data contains a change.
@@ -525,14 +528,12 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
525
528
  }
526
529
  if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
527
530
  const cachedCollection = getCachedCollection(subscriber.key);
528
- const cachedCollectionWithoutNestedNulls = utils_1.default.removeNestedNullValues(cachedCollection);
529
- cachedCollectionWithoutNestedNulls[key] = value;
530
- subscriber.callback(cachedCollectionWithoutNestedNulls);
531
+ cachedCollection[key] = value;
532
+ subscriber.callback(cachedCollection);
531
533
  continue;
532
534
  }
533
- const valueWithoutNestedNulls = utils_1.default.removeNestedNullValues(value);
534
535
  const subscriberCallback = subscriber.callback;
535
- subscriberCallback(valueWithoutNestedNulls, key);
536
+ subscriberCallback(value, key);
536
537
  continue;
537
538
  }
538
539
  // Subscriber connected via withOnyx() HOC
@@ -612,7 +613,7 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
612
613
  * - sets state on the withOnyxInstances
613
614
  * - triggers the callback function
614
615
  */
615
- function sendDataToConnection(mapping, val, matchedKey, isBatched) {
616
+ function sendDataToConnection(mapping, value, matchedKey, isBatched) {
616
617
  var _a, _b;
617
618
  // If the mapping no longer exists then we should not send any data.
618
619
  // This means our subscriber disconnected or withOnyx wrapped component unmounted.
@@ -620,15 +621,15 @@ function sendDataToConnection(mapping, val, matchedKey, isBatched) {
620
621
  return;
621
622
  }
622
623
  if ('withOnyxInstance' in mapping && mapping.withOnyxInstance) {
623
- let newData = val;
624
+ let newData = value;
624
625
  // If the mapping has a selector, then the component's state must only be updated with the data
625
626
  // returned by the selector.
626
627
  if (mapping.selector) {
627
628
  if (isCollectionKey(mapping.key)) {
628
- newData = reduceCollectionWithSelector(val, mapping.selector, mapping.withOnyxInstance.state);
629
+ newData = reduceCollectionWithSelector(value, mapping.selector, mapping.withOnyxInstance.state);
629
630
  }
630
631
  else {
631
- newData = mapping.selector(val, mapping.withOnyxInstance.state);
632
+ newData = mapping.selector(value, mapping.withOnyxInstance.state);
632
633
  }
633
634
  }
634
635
  PerformanceUtils.logSetStateCall(mapping, null, newData, 'sendDataToConnection');
@@ -640,8 +641,13 @@ function sendDataToConnection(mapping, val, matchedKey, isBatched) {
640
641
  }
641
642
  return;
642
643
  }
643
- const valuesWithoutNestedNulls = utils_1.default.removeNestedNullValues(val);
644
- (_b = (_a = mapping).callback) === null || _b === void 0 ? void 0 : _b.call(_a, valuesWithoutNestedNulls, matchedKey);
644
+ // When there are no matching keys in "Onyx.connect", we pass null to "sendDataToConnection" explicitly,
645
+ // to allow the withOnyx instance to set the value in the state initially and therefore stop the loading state once all
646
+ // required keys have been set.
647
+ // If we would pass undefined to setWithOnyxInstance instead, withOnyx would not set the value in the state.
648
+ // withOnyx will internally replace null values with undefined and never pass null values to wrapped components.
649
+ // For regular callbacks, we never want to pass null values, but always just undefined if a value is not set in cache or storage.
650
+ (_b = (_a = mapping).callback) === null || _b === void 0 ? void 0 : _b.call(_a, value === null ? undefined : value, matchedKey);
645
651
  }
646
652
  /**
647
653
  * We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
@@ -682,7 +688,7 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
682
688
  * These missingKeys will be later to use to multiGet the data from the storage.
683
689
  */
684
690
  matchingKeys.forEach((key) => {
685
- const cacheValue = OnyxCache_1.default.getValue(key);
691
+ const cacheValue = OnyxCache_1.default.get(key);
686
692
  if (cacheValue) {
687
693
  data[key] = cacheValue;
688
694
  return;
@@ -755,7 +761,7 @@ function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
755
761
  * Remove a key from Onyx and update the subscribers
756
762
  */
757
763
  function remove(key) {
758
- const prevValue = OnyxCache_1.default.getValue(key, false);
764
+ const prevValue = OnyxCache_1.default.get(key, false);
759
765
  OnyxCache_1.default.drop(key);
760
766
  scheduleSubscriberUpdate(key, null, prevValue);
761
767
  return storage_1.default.removeItem(key).then(() => undefined);
@@ -798,11 +804,11 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
798
804
  /**
799
805
  * Notifies subscribers and writes current value to cache
800
806
  */
801
- function broadcastUpdate(key, value, hasChanged, wasRemoved = false) {
802
- const prevValue = OnyxCache_1.default.getValue(key, false);
807
+ function broadcastUpdate(key, value, hasChanged) {
808
+ const prevValue = OnyxCache_1.default.get(key, false);
803
809
  // Update subscribers if the cached value has changed, or when the subscriber specifically requires
804
810
  // all updates regardless of value changes (indicated by initWithStoredValues set to false).
805
- if (hasChanged && !wasRemoved) {
811
+ if (hasChanged) {
806
812
  OnyxCache_1.default.set(key, value);
807
813
  }
808
814
  else {
@@ -823,7 +829,7 @@ function hasPendingMergeForKey(key) {
823
829
  function removeNullValues(key, value, shouldRemoveNestedNulls = true) {
824
830
  if (value === null) {
825
831
  remove(key);
826
- return { value, wasRemoved: true };
832
+ return { value: null, wasRemoved: true };
827
833
  }
828
834
  // We can remove all null values in an object by merging it with itself
829
835
  // utils.fastMerge recursively goes through the object and removes all null values
@@ -22,14 +22,26 @@ const provider = {
22
22
  throw Error('IDBKeyVal store could not be created');
23
23
  idbKeyValStore = newIdbKeyValStore;
24
24
  },
25
- setItem: (key, value) => (0, idb_keyval_1.set)(key, value, idbKeyValStore),
25
+ setItem: (key, value) => {
26
+ if (value === null) {
27
+ provider.removeItem(key);
28
+ }
29
+ return (0, idb_keyval_1.set)(key, value, idbKeyValStore);
30
+ },
26
31
  multiGet: (keysParam) => (0, idb_keyval_1.getMany)(keysParam, idbKeyValStore).then((values) => values.map((value, index) => [keysParam[index], value])),
27
32
  multiMerge: (pairs) => idbKeyValStore('readwrite', (store) => {
28
33
  // Note: we are using the manual store transaction here, to fit the read and update
29
34
  // of the items in one transaction to achieve best performance.
30
35
  const getValues = Promise.all(pairs.map(([key]) => (0, idb_keyval_1.promisifyRequest)(store.get(key))));
31
36
  return getValues.then((values) => {
32
- const upsertMany = pairs.map(([key, value], index) => {
37
+ const pairsWithoutNull = pairs.filter(([key, value]) => {
38
+ if (value === null) {
39
+ provider.removeItem(key);
40
+ return false;
41
+ }
42
+ return true;
43
+ });
44
+ const upsertMany = pairsWithoutNull.map(([key, value], index) => {
33
45
  const prev = values[index];
34
46
  const newValue = utils_1.default.fastMerge(prev, value);
35
47
  return (0, idb_keyval_1.promisifyRequest)(store.put(newValue, key));
@@ -41,7 +53,16 @@ const provider = {
41
53
  // Since Onyx also merged the existing value with the changes, we can just set the value directly
42
54
  return provider.setItem(key, preMergedValue);
43
55
  },
44
- multiSet: (pairs) => (0, idb_keyval_1.setMany)(pairs, idbKeyValStore),
56
+ multiSet: (pairs) => {
57
+ const pairsWithoutNull = pairs.filter(([key, value]) => {
58
+ if (value === null) {
59
+ provider.removeItem(key);
60
+ return false;
61
+ }
62
+ return true;
63
+ });
64
+ return (0, idb_keyval_1.setMany)(pairsWithoutNull, idbKeyValStore);
65
+ },
45
66
  clear: () => (0, idb_keyval_1.clear)(idbKeyValStore),
46
67
  getAllKeys: () => (0, idb_keyval_1.keys)(idbKeyValStore),
47
68
  getItem: (key) => (0, idb_keyval_1.get)(key, idbKeyValStore)
package/dist/types.d.ts CHANGED
@@ -161,7 +161,7 @@ type Selector<TKey extends OnyxKey, TOnyxProps, TReturnType> = (value: OnyxEntry
161
161
  * })(Component);
162
162
  * ```
163
163
  */
164
- type OnyxEntry<TOnyxValue> = TOnyxValue | null | undefined;
164
+ type OnyxEntry<TOnyxValue> = TOnyxValue | undefined;
165
165
  /**
166
166
  * Represents an Onyx collection of entries, that can be either a record of `TOnyxValue`s or `null` / `undefined` if it is empty or doesn't exist.
167
167
  *
@@ -190,7 +190,7 @@ type OnyxEntry<TOnyxValue> = TOnyxValue | null | undefined;
190
190
  * })(Component);
191
191
  * ```
192
192
  */
193
- type OnyxCollection<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | null | undefined>>;
193
+ type OnyxCollection<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | undefined>>;
194
194
  /** Utility type to extract `TOnyxValue` from `OnyxCollection<TOnyxValue>` */
195
195
  type ExtractOnyxCollectionValue<TOnyxCollection> = TOnyxCollection extends NonNullable<OnyxCollection<infer U>> ? U : never;
196
196
  type NonTransformableTypes = BuiltIns | ((...args: any[]) => unknown) | Map<unknown, unknown> | Set<unknown> | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | unknown[] | readonly unknown[];
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OnyxKey } from './types';
1
+ import type { OnyxKey, OnyxValue } from './types';
2
2
  type EmptyObject = Record<string, never>;
3
3
  type EmptyValue = EmptyObject | null | undefined;
4
4
  /** Checks whether the given object is an object and not null/undefined. */
@@ -12,7 +12,7 @@ declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
12
12
  */
13
13
  declare function fastMerge<TObject extends Record<string, unknown>>(target: TObject | null, source: TObject | null, shouldRemoveNestedNulls?: boolean): TObject | null;
14
14
  /** Deep removes the nested null values from the given value. */
15
- declare function removeNestedNullValues<TObject extends Record<string, unknown>>(value: unknown | unknown[] | TObject): Record<string, unknown> | unknown[] | null;
15
+ declare function removeNestedNullValues<TValue extends OnyxValue<OnyxKey> | unknown | unknown[]>(value: TValue | null): TValue | null;
16
16
  /** Formats the action name by uppercasing and adding the key if provided. */
17
17
  declare function formatActionName(method: string, key?: OnyxKey): string;
18
18
  /** validate that the update and the existing value are compatible */
package/dist/utils.js CHANGED
@@ -90,7 +90,8 @@ function fastMerge(target, source, shouldRemoveNestedNulls = true) {
90
90
  /** Deep removes the nested null values from the given value. */
91
91
  function removeNestedNullValues(value) {
92
92
  if (typeof value === 'object' && !Array.isArray(value)) {
93
- return fastMerge(value, value);
93
+ const objectValue = value;
94
+ return fastMerge(objectValue, objectValue);
94
95
  }
95
96
  return value;
96
97
  }
@@ -36,6 +36,7 @@ const Onyx_1 = __importDefault(require("../Onyx"));
36
36
  const OnyxUtils_1 = __importDefault(require("../OnyxUtils"));
37
37
  const Str = __importStar(require("../Str"));
38
38
  const utils_1 = __importDefault(require("../utils"));
39
+ const OnyxCache_1 = __importDefault(require("../OnyxCache"));
39
40
  // This is a list of keys that can exist on a `mapping`, but are not directly related to loading data from Onyx. When the keys of a mapping are looped over to check
40
41
  // if a key has changed, it's a good idea to skip looking at these properties since they would have unexpected results.
41
42
  const mappingPropertiesToIgnoreChangesTo = ['initialValue', 'allowStaleData'];
@@ -85,7 +86,8 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
85
86
  const cachedState = mapOnyxToStateEntries(mapOnyxToState).reduce((resultObj, [propName, mapping]) => {
86
87
  const key = Str.result(mapping.key, props);
87
88
  let value = OnyxUtils_1.default.tryGetCachedValue(key, mapping);
88
- if (!value && mapping.initialValue) {
89
+ const hasCacheForKey = OnyxCache_1.default.hasCacheForKey(key);
90
+ if (!hasCacheForKey && !value && mapping.initialValue) {
89
91
  value = mapping.initialValue;
90
92
  }
91
93
  /**
@@ -99,7 +101,10 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
99
101
  * In reality, Onyx.merge() will only update the subscriber after all merges have been batched and the previous value is retrieved via a get() (returns a promise).
100
102
  * So, we won't use the cache optimization here as it will lead us to arbitrarily defer various actions in the application code.
101
103
  */
102
- if (mapping.initWithStoredValues !== false && ((value !== undefined && !OnyxUtils_1.default.hasPendingMergeForKey(key)) || mapping.allowStaleData)) {
104
+ const hasPendingMergeForKey = OnyxUtils_1.default.hasPendingMergeForKey(key);
105
+ const hasValueInCache = hasCacheForKey || value !== undefined;
106
+ const shouldSetState = mapping.initWithStoredValues !== false && ((hasValueInCache && !hasPendingMergeForKey) || !!mapping.allowStaleData);
107
+ if (shouldSetState) {
103
108
  // eslint-disable-next-line no-param-reassign
104
109
  resultObj[propName] = value;
105
110
  }
@@ -183,7 +188,7 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
183
188
  * initialValue is there, we just check if the update is different than that and then try to handle it as best as we can.
184
189
  */
185
190
  setWithOnyxState(statePropertyName, val) {
186
- const prevValue = this.state[statePropertyName];
191
+ const prevVal = this.state[statePropertyName];
187
192
  // If the component is not loading (read "mounting"), then we can just update the state
188
193
  // There is a small race condition.
189
194
  // When calling setWithOnyxState we delete the tempState object that is used to hold temporary state updates while the HOC is gathering data.
@@ -193,10 +198,11 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
193
198
  // This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
194
199
  if (!this.state.loading || !this.tempState) {
195
200
  // Performance optimization, do not trigger update with same values
196
- if (prevValue === val || (utils_1.default.isEmptyObject(prevValue) && utils_1.default.isEmptyObject(val))) {
201
+ if (prevVal === val || (utils_1.default.isEmptyObject(prevVal) && utils_1.default.isEmptyObject(val))) {
197
202
  return;
198
203
  }
199
- this.setStateProxy({ [statePropertyName]: val });
204
+ const valueWithoutNull = val === null ? undefined : val;
205
+ this.setStateProxy({ [statePropertyName]: valueWithoutNull });
200
206
  return;
201
207
  }
202
208
  this.tempState[statePropertyName] = val;
@@ -217,16 +223,16 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
217
223
  const initialValue = mapOnyxToState[key].initialValue;
218
224
  // If initialValue is there and the state contains something different it means
219
225
  // an update has already been received and we can discard the value we are trying to hydrate
220
- if (initialValue !== undefined && prevState[key] !== undefined && prevState[key] !== initialValue) {
226
+ if (initialValue !== undefined && prevState[key] !== undefined && prevState[key] !== initialValue && prevState[key] !== null) {
221
227
  // eslint-disable-next-line no-param-reassign
222
228
  result[key] = prevState[key];
223
- // if value is already there (without initial value) then we can discard the value we are trying to hydrate
224
229
  }
225
- else if (prevState[key] !== undefined) {
230
+ else if (prevState[key] !== undefined && prevState[key] !== null) {
231
+ // if value is already there (without initial value) then we can discard the value we are trying to hydrate
226
232
  // eslint-disable-next-line no-param-reassign
227
233
  result[key] = prevState[key];
228
234
  }
229
- else {
235
+ else if (stateUpdate[key] !== null) {
230
236
  // eslint-disable-next-line no-param-reassign
231
237
  result[key] = stateUpdate[key];
232
238
  }
@@ -300,9 +306,8 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
300
306
  // Remove any internal state properties used by withOnyx
301
307
  // that should not be passed to a wrapped component
302
308
  const stateToPass = utils_1.default.omit(this.state, ([stateKey, stateValue]) => stateKey === 'loading' || stateValue === null);
303
- const stateToPassWithoutNestedNulls = utils_1.default.removeNestedNullValues(stateToPass);
304
309
  // Spreading props and state is necessary in an HOC where the data cannot be predicted
305
- return (react_1.default.createElement(WrappedComponent, Object.assign({ markReadyForHydration: this.flushPendingSetStates }, propsToPass, stateToPassWithoutNestedNulls, { ref: this.props.forwardedRef })));
310
+ return (react_1.default.createElement(WrappedComponent, Object.assign({ markReadyForHydration: this.flushPendingSetStates }, propsToPass, stateToPass, { ref: this.props.forwardedRef })));
306
311
  }
307
312
  }
308
313
  withOnyx.displayName = `withOnyx(${displayName})`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.42",
3
+ "version": "2.0.43",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",