Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
<dt><a href="#getSkippableCollectionMemberIDs">getSkippableCollectionMemberIDs()</a></dt>
<dd><p>Getter - returns the skippable collection member IDs.</p>
</dd>
<dt><a href="#getSnapshotMergeKeys">getSnapshotMergeKeys()</a></dt>
<dd><p>Getter - returns the snapshot merge keys allowlist.</p>
</dd>
<dt><a href="#setSkippableCollectionMemberIDs">setSkippableCollectionMemberIDs()</a></dt>
<dd><p>Setter - sets the skippable collection member IDs.</p>
</dd>
<dt><a href="#setSnapshotMergeKeys">setSnapshotMergeKeys()</a></dt>
<dd><p>Setter - sets the snapshot merge keys allowlist.</p>
</dd>
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys)</a></dt>
<dd><p>Sets the initial values for the Onyx store</p>
</dd>
Expand Down Expand Up @@ -222,12 +228,24 @@ Getter - returns the deffered init task.
## getSkippableCollectionMemberIDs()
Getter - returns the skippable collection member IDs.

**Kind**: global function
<a name="getSnapshotMergeKeys"></a>

## getSnapshotMergeKeys()
Getter - returns the snapshot merge keys allowlist.

**Kind**: global function
<a name="setSkippableCollectionMemberIDs"></a>

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

**Kind**: global function
<a name="setSnapshotMergeKeys"></a>

## setSnapshotMergeKeys()
Setter - sets the snapshot merge keys allowlist.

**Kind**: global function
<a name="initStoreValues"></a>

Expand Down
15 changes: 0 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,6 @@ export default App;

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).

### useOnyx()'s `canBeMissing` option

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.

```javascript
const Component = ({reportID}) => {
// This hook will log an alert (via `Logger.logAlert()`) if `report` is `undefined`.
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: false});

// rest of the component's code.
};

export default Component;
```

## Collections

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`).
Expand Down
6 changes: 2 additions & 4 deletions lib/OnyxSnapshotCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,17 @@ class OnyxSnapshotCache {
* - `selector`: Different selectors produce different results, so each selector needs its own cache entry
* - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
* - `allowStaleData`: Controls whether stale data can be returned during pending merges, affecting result timing
* - `canBeMissing`: Determines logging behavior for missing data, but doesn't affect the actual data returned
*
* Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
* or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
*/
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues' | 'allowStaleData' | 'canBeMissing'>): string {
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues' | 'allowStaleData'>): string {
const selectorID = options?.selector ? this.getSelectorID(options.selector) : 'no_selector';

// Create options hash without expensive JSON.stringify
const initWithStoredValues = options?.initWithStoredValues ?? true;
const allowStaleData = options?.allowStaleData ?? false;
const canBeMissing = options?.canBeMissing ?? true;
const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}_${canBeMissing}`;
const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}`;

// Increment reference count for this cache key
const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
Expand Down
27 changes: 2 additions & 25 deletions lib/useOnyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import * as GlobalSettings from './GlobalSettings';
import type {CollectionKeyBase, OnyxKey, OnyxValue} from './types';
import usePrevious from './usePrevious';
import decorateWithMetrics from './metrics';
import * as Logger from './Logger';
import onyxSnapshotCache from './OnyxSnapshotCache';
import useLiveRef from './useLiveRef';

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

/**
* 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.
*/
canBeMissing?: boolean;

/**
* This will be used to subscribe to a subset of an Onyx key's data.
* Using this setting on `useOnyx` can have very positive performance benefits because the component will only re-render
Expand Down Expand Up @@ -156,9 +147,8 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
selector: options?.selector,
initWithStoredValues: options?.initWithStoredValues,
allowStaleData: options?.allowStaleData,
canBeMissing: options?.canBeMissing,
}),
[options?.selector, options?.initWithStoredValues, options?.allowStaleData, options?.canBeMissing],
[options?.selector, options?.initWithStoredValues, options?.allowStaleData],
);

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

let isOnyxValueDefined = true;

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

// This flag is `false` when the original Onyx value (without selector) is not defined yet.
// It will be used later to check if we need to log an alert that the value is missing.
isOnyxValueDefined = value !== null && value !== undefined;

// We set this flag to `false` again since we don't want to get the newest cached value every time `getSnapshot()` is executed,
// and only when `Onyx.connect()` callback is fired.
shouldGetCachedValueRef.current = false;
Expand Down Expand Up @@ -317,21 +301,14 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
sourceValue: sourceValueRef.current,
},
];

// If `canBeMissing` is set to `false` and the Onyx value of that key is not defined,
// we log an alert so it can be acknowledged by the consumer. Additionally, we won't log alerts
// if there's a `Onyx.clear()` task in progress.
if (options?.canBeMissing === false && newFetchStatus === 'loaded' && !isOnyxValueDefined && !OnyxCache.hasPendingTask(TASK.CLEAR)) {
Logger.logAlert(`useOnyx returned no data for key with canBeMissing set to false for key ${key}`, {showAlert: true});
}
}

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

return resultRef.current;
}, [options?.initWithStoredValues, options?.allowStaleData, options?.canBeMissing, key, memoizedSelector, cacheKey, previousKey]);
}, [options?.initWithStoredValues, options?.allowStaleData, key, memoizedSelector, cacheKey, previousKey]);

const subscribe = useCallback(
(onStoreChange: () => void) => {
Expand Down
Loading
Loading