Skip to content
Draft
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
4 changes: 0 additions & 4 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,6 @@ When a collection of keys change, search for any callbacks matching the collecti
When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks

**Kind**: global function
**Example**
```js
keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
```
<a name="sendDataToConnection"></a>

## sendDataToConnection()
Expand Down
11 changes: 3 additions & 8 deletions lib/OnyxConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,26 +115,21 @@ class OnyxConnectionManager {
* according to their purpose and effect they produce in the Onyx connection.
*/
private generateConnectionID<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): string {
const {key, initWithStoredValues, reuseConnection, waitForCollectionCallback} = connectOptions;
const {key, reuseConnection, waitForCollectionCallback} = connectOptions;

// The current session ID is appended to the connection ID so we can have different connections
// after an `Onyx.clear()` operation.
let suffix = `,sessionID=${this.sessionID}`;

// We will generate a unique ID in any of the following situations:
// - `reuseConnection` is `false`. That means the subscriber explicitly wants the connection to not be reused.
// - `initWithStoredValues` is `false`. This flag changes the subscription flow when set to `false`, so the connection can't be reused.
// - `key` is a collection key AND `waitForCollectionCallback` is `undefined/false`. This combination needs a new connection at every subscription
// in order to send all the collection entries, so the connection can't be reused.
if (
reuseConnection === false ||
initWithStoredValues === false ||
(OnyxKeys.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false))
) {
if (reuseConnection === false || (OnyxKeys.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false))) {
suffix += `,uniqueID=${Str.guid()}`;
}

return `onyxKey=${key},initWithStoredValues=${initWithStoredValues ?? true},waitForCollectionCallback=${waitForCollectionCallback ?? false}${suffix}`;
return `onyxKey=${key},waitForCollectionCallback=${waitForCollectionCallback ?? false}${suffix}`;
}

/**
Expand Down
8 changes: 2 additions & 6 deletions lib/OnyxSnapshotCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,13 @@ class OnyxSnapshotCache {
* according to their purpose and effect they produce in the useOnyx hook behavior:
*
* - `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
*
* Other options like `reuseConnection` 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'>): string {
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector'>): string {
const selectorID = options?.selector ? this.getSelectorID(options.selector) : 'no_selector';

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

// Increment reference count for this cache key
const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
Expand Down
11 changes: 1 addition & 10 deletions lib/OnyxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,9 +613,6 @@ function keysChanged<TKey extends CollectionKeyBase>(

/**
* When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
*
* @example
* keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
*/
function keyChanged<TKey extends OnyxKey>(
key: TKey,
Expand Down Expand Up @@ -820,13 +817,11 @@ function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, on
* Notifies subscribers and writes current value to cache
*/
function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean): void {
// Update subscribers if the cached value has changed, or when the subscriber specifically requires
// all updates regardless of value changes (indicated by initWithStoredValues set to false).
if (hasChanged) {
cache.set(key, value);
}

keyChanged(key, value, (subscriber) => hasChanged || subscriber?.initWithStoredValues === false);
keyChanged(key, value, () => !!hasChanged);
}

function hasPendingMergeForKey(key: OnyxKey): boolean {
Expand Down Expand Up @@ -1070,10 +1065,6 @@ function subscribeToKey<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKe
// We create a mapping from key to lists of subscriptionIDs to access the specific list of subscriptionIDs.
storeKeyBySubscriptions(mapping.key, callbackToStateMapping[subscriptionID].subscriptionID);

if (mapping.initWithStoredValues === false) {
return subscriptionID;
}

// Commit connection only after init passes
deferredInitTask.promise
// This first .then() adds a microtask tick for compatibility reasons and
Expand Down
3 changes: 0 additions & 3 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,6 @@ type Collection<TKey extends CollectionKeyBase, TValue> = Record<`${TKey}${strin
/** Represents the base options used in `Onyx.connect()` method. */
// NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method!
type BaseConnectOptions = {
/** If set to `false`, then the initial data will be only sent to the callback function if it changes. */
initWithStoredValues?: boolean;

/**
* If set to `false`, the connection won't be reused between other subscribers that are listening to the same Onyx key
* with the same connect configurations.
Expand Down
33 changes: 7 additions & 26 deletions lib/useOnyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ import useLiveRef from './useLiveRef';
type UseOnyxSelector<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>> = (data: OnyxValue<TKey> | undefined) => TReturnValue;

type UseOnyxOptions<TKey extends OnyxKey, TReturnValue> = {
/**
* If set to `false`, then no data will be prefilled into the component.
* @deprecated This param is going to be removed soon. Use RAM-only keys instead.
*/
initWithStoredValues?: boolean;

/**
* If set to `false`, the connection won't be reused between other subscribers that are listening to the same Onyx key
* with the same connect configurations.
Expand Down Expand Up @@ -97,11 +91,10 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(

// Stores the previously result returned by the hook, containing the data from cache and the fetch status.
// We initialize it to `undefined` and `loading` fetch status to simulate the initial result when the hook is loading from the cache.
// However, if `initWithStoredValues` is `false` we set the fetch status to `loaded` since we want to signal that data is ready.
const resultRef = useRef<UseOnyxResult<TReturnValue>>([
undefined,
{
status: options?.initWithStoredValues === false ? 'loaded' : 'loading',
status: 'loading',
},
]);

Expand Down Expand Up @@ -133,9 +126,8 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
() =>
onyxSnapshotCache.registerConsumer({
selector: options?.selector,
initWithStoredValues: options?.initWithStoredValues,
}),
[options?.selector, options?.initWithStoredValues],
[options?.selector],
);

useEffect(() => () => onyxSnapshotCache.deregisterConsumer(key, cacheKey), [key, cacheKey]);
Expand Down Expand Up @@ -174,26 +166,16 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(

const getSnapshot = useCallback(() => {
// Check if we have any cache for this Onyx key
// Don't use cache for first connection with initWithStoredValues: false
// Also don't use cache during active data updates (when shouldGetCachedValueRef is true)
// Don't use cache during active data updates (when shouldGetCachedValueRef is true)
const isFirstConnection = connectedKeyRef.current !== key;
if (!(isFirstConnection && options?.initWithStoredValues === false) && !shouldGetCachedValueRef.current) {
if (!shouldGetCachedValueRef.current) {
const cachedResult = onyxSnapshotCache.getCachedResult<UseOnyxResult<TReturnValue>>(key, cacheKey);
if (cachedResult !== undefined) {
resultRef.current = cachedResult;
return cachedResult;
}
}

// We return the initial result right away during the first connection if `initWithStoredValues` is set to `false`.
if (isFirstConnection && options?.initWithStoredValues === false) {
const result = resultRef.current;

// Store result in snapshot cache
onyxSnapshotCache.setCachedResult<UseOnyxResult<TReturnValue>>(key, cacheKey, result);
return result;
}

// We get the value from cache while the first connection to Onyx is being made or if the key has changed,
// so we can return any cached value right away. For the case where the key has changed, If we don't return the cached value right away, then the UI will show the incorrect (previous) value for a brief period which looks like a UI glitch to the user. After the connection is made, we only
// update `newValueRef` when `Onyx.connect()` callback is fired.
Expand Down Expand Up @@ -255,7 +237,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
}

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

const subscribe = useCallback(
(onStoreChange: () => void) => {
Expand All @@ -266,7 +248,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
previousValueRef.current = null;
newValueRef.current = null;
sourceValueRef.current = undefined;
resultRef.current = [undefined, {status: options?.initWithStoredValues === false ? 'loaded' : 'loading'}];
resultRef.current = [undefined, {status: 'loading'}];
}
// Force a cache re-read on every (re)subscription so any side effects from
// subscribeToKey (e.g. addNullishStorageKey for skippable collection member ids)
Expand Down Expand Up @@ -299,7 +281,6 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
// Finally, we signal that the store changed, making `getSnapshot()` be called again.
onStoreChange();
},
initWithStoredValues: options?.initWithStoredValues,
waitForCollectionCallback: OnyxKeys.isCollectionKey(key) as true,
reuseConnection: options?.reuseConnection,
});
Expand All @@ -315,7 +296,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
onStoreChangeFnRef.current = null;
};
},
[key, options?.initWithStoredValues, options?.reuseConnection],
[key, options?.reuseConnection],
);

const result = useSyncExternalStore<UseOnyxResult<TReturnValue>>(subscribe, getSnapshot);
Expand Down
2 changes: 0 additions & 2 deletions tests/perf-test/OnyxSnapshotCache.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ const complexSelector: UseOnyxSelector<OnyxKey, ComplexSelectorResult> = (data)

const selectorOptions: UseOnyxOptions<string, number | undefined> = {
selector: simpleSelector,
initWithStoredValues: true,
};

const complexSelectorOptions: UseOnyxOptions<string, ComplexSelectorResult> = {
selector: complexSelector,
initWithStoredValues: true,
};

// Mock results
Expand Down
10 changes: 3 additions & 7 deletions tests/perf-test/OnyxUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ describe('OnyxUtils', () => {
beforeEach: async () => {
await Onyx.multiSet(mockedReportActionsMap);
for (const key of mockedReportActionsKeys) {
const id = OnyxUtils.subscribeToKey({key, callback: jest.fn(), initWithStoredValues: false});
const id = OnyxUtils.subscribeToKey({key, callback: jest.fn()});
subscriptionMap.set(key, id);
}
},
Expand Down Expand Up @@ -340,7 +340,7 @@ describe('OnyxUtils', () => {
beforeEach: async () => {
await Onyx.set(key, previousReportAction);
for (let i = 0; i < 10000; i++) {
const id = OnyxUtils.subscribeToKey({key, callback: jest.fn(), initWithStoredValues: false});
const id = OnyxUtils.subscribeToKey({key, callback: jest.fn()});
subscriptionIDs.add(id);
}
},
Expand Down Expand Up @@ -372,7 +372,7 @@ describe('OnyxUtils', () => {
{
beforeEach: async () => {
await Onyx.multiSet(mockedReportActionsMap);
subscriptionID = OnyxUtils.subscribeToKey({key: collectionKey, callback: jest.fn(), initWithStoredValues: false});
subscriptionID = OnyxUtils.subscribeToKey({key: collectionKey, callback: jest.fn()});
},
afterEach: async () => {
if (subscriptionID) {
Expand Down Expand Up @@ -402,7 +402,6 @@ describe('OnyxUtils', () => {
subscriptionID = OnyxUtils.subscribeToKey({
key: collectionKey,
callback: jest.fn(),
initWithStoredValues: false,
});

OnyxUtils.getCollectionDataAndSendAsObject(mockedReportActionsKeys, {
Expand Down Expand Up @@ -650,7 +649,6 @@ describe('OnyxUtils', () => {
beforeEach: async () => {
subscriptionID = OnyxUtils.subscribeToKey({
key,
initWithStoredValues: false,
});
},
afterEach: clearOnyxAfterEachMeasure,
Expand Down Expand Up @@ -692,7 +690,6 @@ describe('OnyxUtils', () => {
beforeEach: async () => {
subscriptionID = OnyxUtils.subscribeToKey({
key,
initWithStoredValues: false,
});
},
afterEach: async () => {
Expand All @@ -713,7 +710,6 @@ describe('OnyxUtils', () => {
beforeEach: async () => {
subscriptionID = OnyxUtils.subscribeToKey({
key,
initWithStoredValues: false,
});
OnyxUtils.storeKeyBySubscriptions(key, subscriptionID);
},
Expand Down
25 changes: 0 additions & 25 deletions tests/perf-test/useOnyx.perf-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,31 +196,6 @@ describe('useOnyx', () => {
});
});

describe('initWithStoredValues', () => {
/**
* Expected renders: 1.
*/
test('connecting with initWithStoredValues set to false', async () => {
const key = ONYXKEYS.TEST_KEY;
await measureRenders(
<UseOnyxWrapper
onyxKey={key}
onyxOptions={{initWithStoredValues: false}}
/>,
{
beforeEach: async () => {
await StorageMock.setItem(key, 'test');
},
scenario: async () => {
await screen.findByText(dataMatcher(key, undefined));
await screen.findByText(metadataStatusMatcher(key, 'loaded'));
},
afterEach: clearOnyxAfterEachMeasure,
},
);
});
});

describe('multiple calls', () => {
/**
* Expected renders: 2.
Expand Down
Loading
Loading