Skip to content

Commit a9f31c1

Browse files
committed
Merge branch 'main' into feature/sqliteprovider-improvements
2 parents 0f17bdb + 313200b commit a9f31c1

14 files changed

Lines changed: 71 additions & 284 deletions

.github/workflows/reassurePerfTests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ jobs:
3535
retry_on: error
3636
command: node .github/actions/javascript/reassureStabilityCheck
3737
env:
38-
ALLOWED_DURATION_DEVIATION: 10
39-
ALLOWED_RELATIVE_DURATION_DEVIATION: 20
38+
ALLOWED_DURATION_DEVIATION: 20
39+
ALLOWED_RELATIVE_DURATION_DEVIATION: 40
4040
IS_VALIDATING_STABILITY: true
4141

4242
- name: Checkout PR head SHA

API-INTERNAL.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@
2020
<dt><a href="#getSkippableCollectionMemberIDs">getSkippableCollectionMemberIDs()</a></dt>
2121
<dd><p>Getter - returns the skippable collection member IDs.</p>
2222
</dd>
23+
<dt><a href="#getSnapshotMergeKeys">getSnapshotMergeKeys()</a></dt>
24+
<dd><p>Getter - returns the snapshot merge keys allowlist.</p>
25+
</dd>
2326
<dt><a href="#setSkippableCollectionMemberIDs">setSkippableCollectionMemberIDs()</a></dt>
2427
<dd><p>Setter - sets the skippable collection member IDs.</p>
2528
</dd>
29+
<dt><a href="#setSnapshotMergeKeys">setSnapshotMergeKeys()</a></dt>
30+
<dd><p>Setter - sets the snapshot merge keys allowlist.</p>
31+
</dd>
2632
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys)</a></dt>
2733
<dd><p>Sets the initial values for the Onyx store</p>
2834
</dd>
@@ -222,12 +228,24 @@ Getter - returns the deffered init task.
222228
## getSkippableCollectionMemberIDs()
223229
Getter - returns the skippable collection member IDs.
224230

231+
**Kind**: global function
232+
<a name="getSnapshotMergeKeys"></a>
233+
234+
## getSnapshotMergeKeys()
235+
Getter - returns the snapshot merge keys allowlist.
236+
225237
**Kind**: global function
226238
<a name="setSkippableCollectionMemberIDs"></a>
227239

228240
## setSkippableCollectionMemberIDs()
229241
Setter - sets the skippable collection member IDs.
230242

243+
**Kind**: global function
244+
<a name="setSnapshotMergeKeys"></a>
245+
246+
## setSnapshotMergeKeys()
247+
Setter - sets the snapshot merge keys allowlist.
248+
231249
**Kind**: global function
232250
<a name="initStoreValues"></a>
233251

README.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,21 +206,6 @@ export default App;
206206

207207
It's also beneficial to use a [selector](https://github.com/Expensify/react-native-onyx/blob/main/API.md#connectmapping--number) with the mapping in case you need to grab a single item in a collection (like a single report action).
208208

209-
### useOnyx()'s `canBeMissing` option
210-
211-
You must pass the `canBeMissing` configuration flag to `useOnyx` if you want the hook to log an alert when data is missing from Onyx store. Regarding usage in `Expensify/App` repo, if the component calling this is the one loading the data by calling an action, then you should set this to `true`. If the component calling this does not load the data then you should set it to `false`, which means that if the data is not there, it will log an alert, as it means we are using data that no one loaded and that's most probably a bug.
212-
213-
```javascript
214-
const Component = ({reportID}) => {
215-
// This hook will log an alert (via `Logger.logAlert()`) if `report` is `undefined`.
216-
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: false});
217-
218-
// rest of the component's code.
219-
};
220-
221-
export default Component;
222-
```
223-
224209
## Collections
225210

226211
Collections allow keys with similar value types to be subscribed together by subscribing to the collection key. To define one, it must be included in the `ONYXKEYS.COLLECTION` object and it must be suffixed with an underscore. Member keys should use a unique identifier or index after the collection key prefix (e.g. `report_42`).

lib/Onyx.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
346346
if (newValue !== oldValue) {
347347
cache.set(key, newValue);
348348

349-
let collectionKey: string | undefined;
350-
try {
351-
collectionKey = OnyxUtils.getCollectionKey(key);
352-
} catch (e) {
353-
// If getCollectionKey() throws an error it means the key is not a collection key.
354-
collectionKey = undefined;
355-
}
349+
const collectionKey = OnyxUtils.getCollectionKey(key);
356350

357351
if (collectionKey) {
358352
if (!keyValuesToResetAsCollection[collectionKey]) {

lib/OnyxCache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,13 @@ class OnyxCache {
458458
/**
459459
* Get the collection key for a given member key
460460
*/
461-
getCollectionKey(key: OnyxKey): OnyxKey | null {
461+
getCollectionKey(key: OnyxKey): OnyxKey | undefined {
462462
for (const collectionKey of this.collectionKeys) {
463463
if (key.startsWith(collectionKey) && key.length > collectionKey.length) {
464464
return collectionKey;
465465
}
466466
}
467-
return null;
467+
return undefined;
468468
}
469469

470470
/**

lib/OnyxSnapshotCache.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,17 @@ class OnyxSnapshotCache {
6060
* - `selector`: Different selectors produce different results, so each selector needs its own cache entry
6161
* - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
6262
* - `allowStaleData`: Controls whether stale data can be returned during pending merges, affecting result timing
63-
* - `canBeMissing`: Determines logging behavior for missing data, but doesn't affect the actual data returned
6463
*
6564
* Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
6665
* or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
6766
*/
68-
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues' | 'allowStaleData' | 'canBeMissing'>): string {
67+
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues' | 'allowStaleData'>): string {
6968
const selectorID = options?.selector ? this.getSelectorID(options.selector) : 'no_selector';
7069

7170
// Create options hash without expensive JSON.stringify
7271
const initWithStoredValues = options?.initWithStoredValues ?? true;
7372
const allowStaleData = options?.allowStaleData ?? false;
74-
const canBeMissing = options?.canBeMissing ?? true;
75-
const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}_${canBeMissing}`;
73+
const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}`;
7674

7775
// Increment reference count for this cache key
7876
const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
@@ -133,12 +131,10 @@ class OnyxSnapshotCache {
133131
// Always invalidate the exact key
134132
this.snapshotCache.delete(keyToInvalidate);
135133

136-
try {
137-
// Check if the key is a collection member and invalidate the collection base key
138-
const collectionBaseKey = OnyxUtils.getCollectionKey(keyToInvalidate);
134+
// Check if the key is a collection member and invalidate the collection base key
135+
const collectionBaseKey = OnyxUtils.getCollectionKey(keyToInvalidate);
136+
if (collectionBaseKey) {
139137
this.snapshotCache.delete(collectionBaseKey);
140-
} catch (e) {
141-
// do nothing - this just means the key is not a collection member
142138
}
143139
}
144140

lib/OnyxUtils.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -475,14 +475,9 @@ function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collect
475475
* @returns true if the key is a collection member, false otherwise
476476
*/
477477
function isCollectionMember(key: OnyxKey): boolean {
478-
try {
479-
const collectionKey = getCollectionKey(key);
480-
// If the key is longer than the collection key, it's a collection member
481-
return key.length > collectionKey.length;
482-
} catch (e) {
483-
// If getCollectionKey throws, the key is not a collection member
484-
return false;
485-
}
478+
const collectionKey = getCollectionKey(key);
479+
// If the key is longer than the collection key, it's a collection member
480+
return !!collectionKey && key.length > collectionKey.length;
486481
}
487482

488483
/**
@@ -503,12 +498,9 @@ function isCollectionMember(key: OnyxKey): boolean {
503498
* @returns true if key is a RAM-only key, RAM-only collection key, or a RAM-only collection member
504499
*/
505500
function isRamOnlyKey(key: OnyxKey): boolean {
506-
try {
507-
const collectionKey = getCollectionKey(key);
508-
// If collectionKey exists for a given key, check if it's a RAM-only key
501+
const collectionKey = getCollectionKey(key);
502+
if (collectionKey) {
509503
return cache.isRamOnlyKey(collectionKey);
510-
} catch {
511-
// If getCollectionKey throws, the key is not a collection member
512504
}
513505

514506
return cache.isRamOnlyKey(key);
@@ -530,8 +522,12 @@ function splitCollectionMemberKey<TKey extends CollectionKey, CollectionKeyType
530522
}
531523

532524
if (!collectionKey) {
525+
const resolvedKey = getCollectionKey(key);
526+
if (!resolvedKey) {
527+
throw new Error(`Invalid '${key}' key provided, only collection keys are allowed.`);
528+
}
533529
// eslint-disable-next-line no-param-reassign
534-
collectionKey = getCollectionKey(key);
530+
collectionKey = resolvedKey;
535531
}
536532

537533
return [collectionKey as CollectionKeyType, key.slice(collectionKey.length)];
@@ -555,9 +551,9 @@ function isKeyMatch(configKey: OnyxKey, key: OnyxKey): boolean {
555551
* - `getCollectionKey("sharedNVP_user_-1_something")` would return "sharedNVP_user_"
556552
*
557553
* @param key - The collection key to process.
558-
* @returns The plain collection key or throws an Error if the key is not a collection one.
554+
* @returns The plain collection key or undefined if the key is not a collection one.
559555
*/
560-
function getCollectionKey(key: CollectionKey): string {
556+
function getCollectionKey(key: CollectionKey): string | undefined {
561557
// Start by finding the position of the last underscore in the string
562558
let lastUnderscoreIndex = key.lastIndexOf('_');
563559

@@ -575,7 +571,7 @@ function getCollectionKey(key: CollectionKey): string {
575571
lastUnderscoreIndex = key.lastIndexOf('_', lastUnderscoreIndex - 1);
576572
}
577573

578-
throw new Error(`Invalid '${key}' key provided, only collection keys are allowed.`);
574+
return undefined;
579575
}
580576

581577
/**
@@ -754,13 +750,7 @@ function keyChanged<TKey extends OnyxKey>(
754750
// do the same in keysChanged, because we only call that function when a collection key changes, and it doesn't happen that often.
755751
// For performance reason, we look for the given key and later if don't find it we look for the collection key, instead of checking if it is a collection key first.
756752
let stateMappingKeys = onyxKeyToSubscriptionIDs.get(key) ?? [];
757-
let collectionKey: string | undefined;
758-
try {
759-
collectionKey = getCollectionKey(key);
760-
} catch (e) {
761-
// If getCollectionKey() throws an error it means the key is not a collection key.
762-
collectionKey = undefined;
763-
}
753+
const collectionKey = getCollectionKey(key);
764754

765755
if (collectionKey) {
766756
// Getting the collection key from the specific key because only collection keys were stored in the mapping.

lib/useOnyx.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import * as GlobalSettings from './GlobalSettings';
99
import type {CollectionKeyBase, OnyxKey, OnyxValue} from './types';
1010
import usePrevious from './usePrevious';
1111
import decorateWithMetrics from './metrics';
12-
import * as Logger from './Logger';
1312
import onyxSnapshotCache from './OnyxSnapshotCache';
1413
import useLiveRef from './useLiveRef';
1514

@@ -43,14 +42,6 @@ type UseOnyxOptions<TKey extends OnyxKey, TReturnValue> = {
4342
*/
4443
allowDynamicKey?: boolean;
4544

46-
/**
47-
* If the component calling this is the one loading the data by calling an action, then you should set this to `true`.
48-
*
49-
* If the component calling this does not load the data then you should set it to `false`, which means that if the data
50-
* is not there, it will log an alert, as it means we are using data that no one loaded and that's most probably a bug.
51-
*/
52-
canBeMissing?: boolean;
53-
5445
/**
5546
* This will be used to subscribe to a subset of an Onyx key's data.
5647
* Using this setting on `useOnyx` can have very positive performance benefits because the component will only re-render
@@ -156,9 +147,8 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
156147
selector: options?.selector,
157148
initWithStoredValues: options?.initWithStoredValues,
158149
allowStaleData: options?.allowStaleData,
159-
canBeMissing: options?.canBeMissing,
160150
}),
161-
[options?.selector, options?.initWithStoredValues, options?.allowStaleData, options?.canBeMissing],
151+
[options?.selector, options?.initWithStoredValues, options?.allowStaleData],
162152
);
163153

164154
useEffect(() => () => onyxSnapshotCache.deregisterConsumer(key, cacheKey), [key, cacheKey]);
@@ -242,8 +232,6 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
242232
}
243233
}
244234

245-
let isOnyxValueDefined = true;
246-
247235
// We return the initial result right away during the first connection if `initWithStoredValues` is set to `false`.
248236
if (isFirstConnectionRef.current && options?.initWithStoredValues === false) {
249237
const result = resultRef.current;
@@ -262,10 +250,6 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
262250
const selectedValue = memoizedSelector ? memoizedSelector(value) : value;
263251
newValueRef.current = (selectedValue ?? undefined) as TReturnValue | undefined;
264252

265-
// This flag is `false` when the original Onyx value (without selector) is not defined yet.
266-
// It will be used later to check if we need to log an alert that the value is missing.
267-
isOnyxValueDefined = value !== null && value !== undefined;
268-
269253
// We set this flag to `false` again since we don't want to get the newest cached value every time `getSnapshot()` is executed,
270254
// and only when `Onyx.connect()` callback is fired.
271255
shouldGetCachedValueRef.current = false;
@@ -317,21 +301,14 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
317301
sourceValue: sourceValueRef.current,
318302
},
319303
];
320-
321-
// If `canBeMissing` is set to `false` and the Onyx value of that key is not defined,
322-
// we log an alert so it can be acknowledged by the consumer. Additionally, we won't log alerts
323-
// if there's a `Onyx.clear()` task in progress.
324-
if (options?.canBeMissing === false && newFetchStatus === 'loaded' && !isOnyxValueDefined && !OnyxCache.hasPendingTask(TASK.CLEAR)) {
325-
Logger.logAlert(`useOnyx returned no data for key with canBeMissing set to false for key ${key}`, {showAlert: true});
326-
}
327304
}
328305

329306
if (newFetchStatus !== 'loading') {
330307
onyxSnapshotCache.setCachedResult<UseOnyxResult<TReturnValue>>(key, cacheKey, resultRef.current);
331308
}
332309

333310
return resultRef.current;
334-
}, [options?.initWithStoredValues, options?.allowStaleData, options?.canBeMissing, key, memoizedSelector, cacheKey, previousKey]);
311+
}, [options?.initWithStoredValues, options?.allowStaleData, key, memoizedSelector, cacheKey, previousKey]);
335312

336313
const subscribe = useCallback(
337314
(onStoreChange: () => void) => {

0 commit comments

Comments
 (0)