diff --git a/API-INTERNAL.md b/API-INTERNAL.md
index 3f63741aa..b853a457a 100644
--- a/API-INTERNAL.md
+++ b/API-INTERNAL.md
@@ -89,9 +89,7 @@ If the requested key is a collection, it will return an object with all the coll
We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
@@ -368,9 +366,7 @@ keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
## sendDataToConnection()
-Sends the data obtained from the keys to the connection. It either:
- - sets state on the withOnyxInstances
- - triggers the callback function
+Sends the data obtained from the keys to the connection.
**Kind**: global function
diff --git a/API.md b/API.md
index d3de3101a..545adbfab 100644
--- a/API.md
+++ b/API.md
@@ -81,10 +81,7 @@ This method will be deprecated soon. Please use `Onyx.connectWithoutView()` inst
| connectOptions.key | The Onyx key to subscribe to. |
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
| connectOptions.waitForCollectionCallback | If set to `true`, it will return the entire collection to the callback as a single object. |
-| connectOptions.withOnyxInstance | The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.statePropertyName | The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.displayName | The component's display name. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.** Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
+| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.** Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
**Example**
```ts
@@ -107,10 +104,7 @@ Connects to an Onyx key given the options passed and listens to its changes.
| connectOptions.key | The Onyx key to subscribe to. |
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
| connectOptions.waitForCollectionCallback | If set to `true`, it will return the entire collection to the callback as a single object. |
-| connectOptions.withOnyxInstance | The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.statePropertyName | The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.displayName | The component's display name. **Only used inside `withOnyx()` HOC.** |
-| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.** Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
+| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.** Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
**Example**
```ts
diff --git a/README.md b/README.md
index 9104ddf3f..03d238fa8 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Awesome persistent storage solution wrapped in a Pub/Sub library.
- Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed
- Anything needing to read Onyx data needs to:
1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > local storage)
- 2. Subscribe to changes of the data for a particular key or set of keys. React function components use the `useOnyx()` hook (recommended), both class and function components can use `withOnyx()` HOC (deprecated, not-recommended) and non-React libs use `Onyx.connect()`.
+ 2. Subscribe to changes of the data for a particular key or set of keys. React function components use the `useOnyx()` hook and non-React libs use `Onyx.connect()`.
3. Get initialized with the current value of that key from persistent storage (Onyx does this by calling `setState()` or triggering the `callback` with the values currently on disk as part of the connection process)
- Subscribing to Onyx keys is done using a constant defined in `ONYXKEYS`. Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (e.g. display a list of them in the nav menu), then it would subscribe to the key `ONYXKEYS.COLLECTION.REPORT`.
@@ -136,7 +136,7 @@ To teardown the subscription call `Onyx.disconnect()` with the `connectionID` re
Onyx.disconnect(connectionID);
```
-We can also access values inside React function components via the `useOnyx()` [hook](https://react.dev/reference/react/hooks) (recommended) or class and function components via the `withOnyx()` [higher order component](https://reactjs.org/docs/higher-order-components.html) (deprecated, not-recommended). When the data changes the component will re-render.
+We can also access values inside React function components via the `useOnyx()` [hook](https://react.dev/reference/react/hooks). When the data changes the component will re-render.
```javascript
import React from 'react';
@@ -168,48 +168,6 @@ if (reportsResult.status === 'loading' || sessionResult.status === 'loading') {
// rest of the component's code.
```
-> [!warning]
-> ## Deprecated Note
-> Please note that the `withOnyx()` Higher Order Component (HOC) is now considered deprecated. Use `useOnyx()` hook instead.
-
-```javascript
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-
-const App = ({session}) => (
-
- {session.token ? Logged in : Logged out }
-
-);
-
-export default withOnyx({
- session: {
- key: ONYXKEYS.SESSION,
- },
-})(App);
-```
-
-Differently from `useOnyx()`, `withOnyx()` will delay the rendering of the wrapped component until all keys/entities have been fetched and passed to the component, this can be convenient for simple cases. This however, can really delay your application if many entities are connected to the same component.
-
-Additionally, if your component has many keys/entities when your component will mount but will receive many updates as data is fetched from DB and passed down to it, as every key that gets fetched will trigger a `setState` on the `withOnyx` HOC. This might cause re-renders on the initial mounting, preventing the component from mounting/rendering in reasonable time, making your app feel slow and even delaying animations.
-
-You can workaround this by passing an additional object with the `shouldDelayUpdates` property set to true. Onyx will then put all the updates in a queue until you decide when then should be applied, the component will receive a function `markReadyForHydration`. A good place to call this function is on the `onLayout` method, which gets triggered after your component has been rendered.
-
-```javascript
-const App = ({session, markReadyForHydration}) => (
- markReadyForHydration()}>
- {session.token ? Logged in : Logged out }
-
-);
-
-// Second argument to funciton is `shouldDelayUpdates`
-export default withOnyx({
- session: {
- key: ONYXKEYS.SESSION
- },
-}, true)(App);
-```
-
### Dependent Onyx Keys and useOnyx()
Some components need to subscribe to multiple Onyx keys at once and sometimes, one key might rely on the data from another key. This is similar to a JOIN in SQL.
@@ -246,24 +204,6 @@ export default App;
* It is VERY important to NOT use empty string default values like `report.policyID || ''`. This results in the key returned to `useOnyx` as `policies_`, which subscribes to the ENTIRE POLICY COLLECTION and is most assuredly not what you were intending. You can use a default of `0` (as long as you are reasonably sure that there is never a policyID=0). This allows Onyx to return `undefined` as the value of the policy key, which is handled by `useOnyx` appropriately.
-**Detailed explanation of how this is handled and rendered with `withOnyx` HOC:**
-1. The component mounts with a `reportID={1234}` prop
-2. `withOnyx` evaluates the mapping
-3. `withOnyx` connects to the key `reports_1234` because of the prop passed to the component
-3. `withOnyx` connects to the key `policies_undefined` because `report` doesn't exist in the props yet, so the `policyID` defaults to `undefined`. * (see note below)
-4. Onyx reads the data and updates the state of `withOnyx` with:
- - `report={{reportID: 1234, policyID: 1, ... the rest of the object ...}}`
- - `policy={undefined}` (since there is no policy with ID `undefined`)
-5. There is still an `undefined` key in the mapping, so Onyx reads the data again
-6. This time `withOnyx` connects to the key `policies_1` because the `report` object exists in the component's state and it has a `policyID: 1`
-7. Onyx reads the data and updates the state of withOnyx with:
- - `policy={{policyID: 1, ... the rest of the object ...}`
-8. Now all mappings have values that are defined (not undefined) and the component is rendered with all necessary data
-
-* It is VERY important to NOT use empty string default values like `report.policyID || ''`. This results in the key returned to `withOnyx` as `policies_` which subscribes to the ENTIRE POLICY COLLECTION and is most assuredly not what you were intending. You can use a default of `0` (as long as you are reasonably sure that there is never a policyID=0). This allows Onyx to return `undefined` as the value of the policy key, which is handled by `withOnyx` appropriately.
-
-DO NOT use more than one `withOnyx` component at a time. It adds overhead and prevents some optimizations like batched rendering from working to its full potential.
-
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
diff --git a/lib/Onyx.ts b/lib/Onyx.ts
index be2878f6a..b2cec4b8c 100644
--- a/lib/Onyx.ts
+++ b/lib/Onyx.ts
@@ -1,6 +1,5 @@
import * as Logger from './Logger';
import cache, {TASK} from './OnyxCache';
-import * as PerformanceUtils from './PerformanceUtils';
import Storage from './storage';
import utils from './utils';
import DevTools from './DevTools';
@@ -11,7 +10,6 @@ import type {
ConnectOptions,
InitOptions,
KeyValueMapping,
- Mapping,
OnyxInputKeyValueMapping,
OnyxCollection,
MixedOperationsQueue,
@@ -41,7 +39,6 @@ function init({
evictableKeys = [],
maxCachedKeysCount = 1000,
shouldSyncMultipleInstances = !!global.localStorage,
- debugSetState = false,
enablePerformanceMetrics = false,
skippableCollectionMemberIDs = [],
}: InitOptions): void {
@@ -56,16 +53,11 @@ function init({
if (shouldSyncMultipleInstances) {
Storage.keepInstancesSync?.((key, value) => {
- const prevValue = cache.get(key, false) as OnyxValue;
cache.set(key, value);
- OnyxUtils.keyChanged(key, value as OnyxValue, prevValue);
+ OnyxUtils.keyChanged(key, value as OnyxValue);
});
}
- if (debugSetState) {
- PerformanceUtils.setShouldDebugSetState(true);
- }
-
if (maxCachedKeysCount > 0) {
cache.setRecentKeysLimit(maxCachedKeysCount);
}
@@ -94,11 +86,8 @@ function init({
* @param connectOptions.key The Onyx key to subscribe to.
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
* @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
- * @param connectOptions.withOnyxInstance The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.statePropertyName The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.displayName The component's display name. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.**
- * Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render
+ * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.**
+ * Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
* when the subset of data changes. Otherwise, any change of data on any property would normally
* cause the component to re-render (and that can be expensive from a performance standpoint).
* @returns The connection object to use when calling `Onyx.disconnect()`.
@@ -122,11 +111,8 @@ function connect(connectOptions: ConnectOptions): Co
* @param connectOptions.key The Onyx key to subscribe to.
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
* @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
- * @param connectOptions.withOnyxInstance The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.statePropertyName The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.displayName The component's display name. **Only used inside `withOnyx()` HOC.**
- * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.**
- * Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render
+ * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.**
+ * Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
* when the subset of data changes. Otherwise, any change of data on any property would normally
* cause the component to re-render (and that can be expensive from a performance standpoint).
* @returns The connection object to use when calling `Onyx.disconnect()`.
@@ -260,7 +246,6 @@ function multiSet(data: OnyxMultiSetInput): Promise {
const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);
const updatePromises = keyValuePairsToSet.map(([key, value]) => {
- const prevValue = cache.get(key, false);
// When we use multiSet to set a key we want to clear the current delta changes from Onyx.merge that were queued
// before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
if (OnyxUtils.hasPendingMergeForKey(key)) {
@@ -269,7 +254,7 @@ function multiSet(data: OnyxMultiSetInput): Promise {
// Update cache and optimistically inform subscribers on the next tick
cache.set(key, value);
- return OnyxUtils.scheduleSubscriberUpdate(key, value, prevValue);
+ return OnyxUtils.scheduleSubscriberUpdate(key, value);
});
return Storage.multiSet(keyValuePairsToSet)
@@ -476,7 +461,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise {
// Notify the subscribers for each key/value group so they can receive the new values
Object.entries(keyValuesToResetIndividually).forEach(([key, value]) => {
- updatePromises.push(OnyxUtils.scheduleSubscriberUpdate(key, value, cache.get(key, false)));
+ updatePromises.push(OnyxUtils.scheduleSubscriberUpdate(key, value));
});
Object.entries(keyValuesToResetAsCollection).forEach(([key, value]) => {
updatePromises.push(OnyxUtils.scheduleNotifyCollectionSubscribers(key, value));
@@ -764,4 +749,4 @@ function applyDecorators() {
}
export default Onyx;
-export type {OnyxUpdate, Mapping, ConnectOptions, SetOptions};
+export type {OnyxUpdate, ConnectOptions, SetOptions};
diff --git a/lib/OnyxConnectionManager.ts b/lib/OnyxConnectionManager.ts
index 3f696b31b..97f71a55b 100644
--- a/lib/OnyxConnectionManager.ts
+++ b/lib/OnyxConnectionManager.ts
@@ -4,7 +4,6 @@ import type {ConnectOptions} from './Onyx';
import OnyxUtils from './OnyxUtils';
import * as Str from './Str';
import type {CollectionConnectCallback, DefaultConnectCallback, DefaultConnectOptions, OnyxKey, OnyxValue} from './types';
-import utils from './utils';
import cache from './OnyxCache';
import onyxSnapshotCache from './OnyxSnapshotCache';
@@ -72,7 +71,7 @@ type Connection = {
};
/**
- * Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers.
+ * Manages Onyx connections of `Onyx.connect()` and `useOnyx()` subscribers.
*/
class OnyxConnectionManager {
/**
@@ -127,12 +126,10 @@ class OnyxConnectionManager {
// - `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.
- // - `withOnyxInstance` is defined inside `connectOptions`. That means the subscriber is a `withOnyx` HOC and therefore doesn't support connection reuse.
if (
reuseConnection === false ||
initWithStoredValues === false ||
- (!utils.hasWithOnyxInstance(connectOptions) && OnyxUtils.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false)) ||
- utils.hasWithOnyxInstance(connectOptions)
+ (OnyxUtils.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false))
) {
suffix += `,uniqueID=${Str.guid()}`;
}
@@ -170,24 +167,18 @@ class OnyxConnectionManager {
// If there is no connection yet for that connection ID, we create a new one.
if (!connectionMetadata) {
- let callback: ConnectCallback | undefined;
-
- // If the subscriber is a `withOnyx` HOC we don't define `callback` as the HOC will use
- // its own logic to handle the data.
- if (!utils.hasWithOnyxInstance(connectOptions)) {
- callback = (value, key, sourceValue) => {
- const createdConnection = this.connectionsMap.get(connectionID);
- if (createdConnection) {
- // We signal that the first connection was made and now any new subscribers
- // can fire their callbacks immediately with the cached value when connecting.
- createdConnection.isConnectionMade = true;
- createdConnection.cachedCallbackValue = value;
- createdConnection.cachedCallbackKey = key;
- createdConnection.sourceValue = sourceValue;
- this.fireCallbacks(connectionID);
- }
- };
- }
+ const callback: ConnectCallback = (value, key, sourceValue) => {
+ const createdConnection = this.connectionsMap.get(connectionID);
+ if (createdConnection) {
+ // We signal that the first connection was made and now any new subscribers
+ // can fire their callbacks immediately with the cached value when connecting.
+ createdConnection.isConnectionMade = true;
+ createdConnection.cachedCallbackValue = value;
+ createdConnection.cachedCallbackKey = key;
+ createdConnection.sourceValue = sourceValue;
+ this.fireCallbacks(connectionID);
+ }
+ };
subscriptionID = OnyxUtils.subscribeToKey({
...connectOptions,
diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts
index b6e1bf8db..39e8afd06 100644
--- a/lib/OnyxUtils.ts
+++ b/lib/OnyxUtils.ts
@@ -1,6 +1,5 @@
/* eslint-disable no-continue */
import {deepEqual} from 'fast-equals';
-import lodashClone from 'lodash/clone';
import type {ValueOf} from 'type-fest';
import lodashPick from 'lodash/pick';
import _ from 'underscore';
@@ -8,7 +7,6 @@ import DevTools from './DevTools';
import * as Logger from './Logger';
import type Onyx from './Onyx';
import cache, {TASK} from './OnyxCache';
-import * as PerformanceUtils from './PerformanceUtils';
import * as Str from './Str';
import unstable_batchedUpdates from './batch';
import Storage from './storage';
@@ -20,7 +18,7 @@ import type {
DefaultConnectCallback,
DefaultConnectOptions,
KeyValueMapping,
- Mapping,
+ CallbackToStateMapping,
MultiMergeReplaceNullPatches,
OnyxCollection,
OnyxEntry,
@@ -34,7 +32,6 @@ import type {
} from './types';
import type {FastMergeOptions, FastMergeResult} from './utils';
import utils from './utils';
-import type {WithOnyxState} from './withOnyx/types';
import type {DeferredTask} from './createDeferredTask';
import createDeferredTask from './createDeferredTask';
import * as GlobalSettings from './GlobalSettings';
@@ -59,7 +56,7 @@ let mergeQueue: Record>> = {};
let mergeQueuePromise: Record> = {};
// Holds a mapping of all the React components that want their state subscribed to a store key
-let callbackToStateMapping: Record> = {};
+let callbackToStateMapping: Record> = {};
// Keeps a copy of the values of the onyx collection keys as a map for faster lookups
let onyxCollectionKeySet = new Set();
@@ -236,14 +233,13 @@ function batchUpdates(updates: () => void): Promise {
* and runs it through a reducer function to return a subset of the data according to a selector.
* The resulting collection will only contain items that are returned by the selector.
*/
-function reduceCollectionWithSelector(
+function reduceCollectionWithSelector(
collection: OnyxCollection,
- selector: Selector,
- withOnyxInstanceState: WithOnyxState | undefined,
+ selector: Selector,
): Record {
return Object.entries(collection ?? {}).reduce((finalCollection: Record, [key, item]) => {
// eslint-disable-next-line no-param-reassign
- finalCollection[key] = selector(item, withOnyxInstanceState);
+ finalCollection[key] = selector(item);
return finalCollection;
}, {});
@@ -528,7 +524,7 @@ function getCollectionKey(key: CollectionKey): string {
* Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
* If the requested key is a collection, it will return an object with all the collection members.
*/
-function tryGetCachedValue(key: TKey, mapping?: Partial>): OnyxValue {
+function tryGetCachedValue(key: TKey): OnyxValue {
let val = cache.get(key);
if (isCollectionKey(key)) {
@@ -545,14 +541,6 @@ function tryGetCachedValue(key: TKey, mapping?: Partial, mapping.selector, state);
- }
- return mapping.selector(val, state);
- }
-
return val;
}
@@ -610,7 +598,6 @@ function keysChanged(
partialCollection: OnyxCollection,
partialPreviousCollection: OnyxCollection | undefined,
notifyConnectSubscribers = true,
- notifyWithOnyxSubscribers = true,
): void {
// We prepare the "cached collection" which is the entire collection + the new partial data that
// was merged in via mergeCollection().
@@ -686,109 +673,6 @@ function keysChanged(
continue;
}
-
- // React component subscriber found.
- if (utils.hasWithOnyxInstance(subscriber)) {
- if (!notifyWithOnyxSubscribers) {
- continue;
- }
-
- // We are subscribed to a collection key so we must update the data in state with the new
- // collection member key values from the partial update.
- if (isSubscribedToCollectionKey) {
- // If the subscriber has a selector, then the component's state must only be updated with the data
- // returned by the selector.
- const collectionSelector = subscriber.selector;
- if (collectionSelector) {
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const previousData = prevState[subscriber.statePropertyName];
- const newData = reduceCollectionWithSelector(cachedCollection, collectionSelector, subscriber.withOnyxInstance.state);
-
- if (deepEqual(previousData, newData)) {
- return null;
- }
-
- return {
- [subscriber.statePropertyName]: newData,
- };
- });
- continue;
- }
-
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevCollection = prevState?.[subscriber.statePropertyName] ?? {};
- const finalCollection = lodashClone(prevCollection);
- const dataKeys = Object.keys(partialCollection ?? {});
- for (const dataKey of dataKeys) {
- finalCollection[dataKey] = cachedCollection[dataKey];
- }
-
- if (deepEqual(prevCollection, finalCollection)) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, prevState?.[subscriber.statePropertyName], finalCollection, 'keysChanged', collectionKey);
- return {
- [subscriber.statePropertyName]: finalCollection,
- };
- });
- continue;
- }
-
- // If a React component is only interested in a single key then we can set the cached value directly to the state name.
- if (isSubscribedToCollectionMemberKey) {
- if (deepEqual(cachedCollection[subscriber.key], previousCollection[subscriber.key])) {
- continue;
- }
-
- // However, we only want to update this subscriber if the partial data contains a change.
- // Otherwise, we would update them with a value they already have and trigger an unnecessary re-render.
- const dataFromCollection = partialCollection?.[subscriber.key];
- if (dataFromCollection === undefined) {
- continue;
- }
-
- // If the subscriber has a selector, then the component's state must only be updated with the data
- // returned by the selector and the state should only change when the subset of data changes from what
- // it was previously.
- const selector = subscriber.selector;
- if (selector) {
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevData = prevState[subscriber.statePropertyName];
- const newData = selector(cachedCollection[subscriber.key], subscriber.withOnyxInstance.state);
-
- if (deepEqual(prevData, newData)) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
- return {
- [subscriber.statePropertyName]: newData,
- };
- });
- continue;
- }
-
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevData = prevState[subscriber.statePropertyName];
- const newData = cachedCollection[subscriber.key];
-
- // Avoids triggering unnecessary re-renders when feeding empty objects
- if (utils.isEmptyObject(newData) && utils.isEmptyObject(prevData)) {
- return null;
- }
-
- if (deepEqual(prevData, newData)) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
- return {
- [subscriber.statePropertyName]: newData,
- };
- });
- }
- }
}
}
@@ -801,10 +685,8 @@ function keysChanged(
function keyChanged(
key: TKey,
value: OnyxValue,
- previousValue: OnyxValue,
- canUpdateSubscriber: (subscriber?: Mapping) => boolean = () => true,
+ canUpdateSubscriber: (subscriber?: CallbackToStateMapping) => boolean = () => true,
notifyConnectSubscribers = true,
- notifyWithOnyxSubscribers = true,
): void {
// Add or remove this key from the recentlyAccessedKeys lists
if (value !== null) {
@@ -814,8 +696,7 @@ function keyChanged(
}
// We get the subscribers interested in the key that has just changed. If the subscriber's key is a collection key then we will
- // notify them if the key that changed is a collection member. Or if it is a regular key notify them when there is an exact match. Depending on whether the subscriber
- // was connected via withOnyx we will call setState() directly on the withOnyx instance. If it is a regular connection we will pass the data to the provided callback.
+ // notify them if the key that changed is a collection member. Or if it is a regular key notify them when there is an exact match.
// Given the amount of times this function is called we need to make sure we are not iterating over all subscribers every time. On the other hand, we don't need to
// do the same in keysChanged, because we only call that function when a collection key changes, and it doesn't happen that often.
// 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.
@@ -873,140 +754,20 @@ function keyChanged(
continue;
}
- // Subscriber connected via withOnyx() HOC
- if (utils.hasWithOnyxInstance(subscriber)) {
- if (!notifyWithOnyxSubscribers) {
- continue;
- }
-
- const selector = subscriber.selector;
- // Check if we are subscribing to a collection key and overwrite the collection member key value in state
- if (isCollectionKey(subscriber.key)) {
- // If the subscriber has a selector, then the consumer of this data must only be given the data
- // returned by the selector and only when the selected data has changed.
- if (selector) {
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevWithOnyxData = prevState[subscriber.statePropertyName];
- const newWithOnyxData = {
- [key]: selector(value, subscriber.withOnyxInstance.state),
- };
- const prevDataWithNewData = {
- ...prevWithOnyxData,
- ...newWithOnyxData,
- };
-
- if (deepEqual(prevWithOnyxData, prevDataWithNewData)) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
- return {
- [subscriber.statePropertyName]: prevDataWithNewData,
- };
- });
- continue;
- }
-
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevCollection = prevState[subscriber.statePropertyName] || {};
- const newCollection = {
- ...prevCollection,
- [key]: value,
- };
-
- if (deepEqual(prevCollection, newCollection)) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, prevCollection, newCollection, 'keyChanged', key);
- return {
- [subscriber.statePropertyName]: newCollection,
- };
- });
- continue;
- }
-
- // If the subscriber has a selector, then the component's state must only be updated with the data
- // returned by the selector and only if the selected data has changed.
- if (selector) {
- subscriber.withOnyxInstance.setStateProxy(() => {
- const prevValue = selector(previousValue, subscriber.withOnyxInstance.state);
- const newValue = selector(value, subscriber.withOnyxInstance.state);
-
- if (deepEqual(prevValue, newValue)) {
- return null;
- }
-
- return {
- [subscriber.statePropertyName]: newValue,
- };
- });
- continue;
- }
-
- // If we did not match on a collection key then we just set the new data to the state property
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
- const prevWithOnyxValue = prevState[subscriber.statePropertyName];
-
- // Avoids triggering unnecessary re-renders when feeding empty objects
- if (utils.isEmptyObject(value) && utils.isEmptyObject(prevWithOnyxValue)) {
- return null;
- }
- if (prevWithOnyxValue === value) {
- return null;
- }
-
- PerformanceUtils.logSetStateCall(subscriber, previousValue, value, 'keyChanged', key);
- return {
- [subscriber.statePropertyName]: value,
- };
- });
- continue;
- }
-
- console.error('Warning: Found a matching subscriber to a key that changed, but no callback or withOnyxInstance could be found.');
+ console.error('Warning: Found a matching subscriber to a key that changed, but no callback could be found.');
}
}
/**
- * Sends the data obtained from the keys to the connection. It either:
- * - sets state on the withOnyxInstances
- * - triggers the callback function
+ * Sends the data obtained from the keys to the connection.
*/
-function sendDataToConnection(mapping: Mapping, value: OnyxValue | null, matchedKey: TKey | undefined, isBatched: boolean): void {
+function sendDataToConnection(mapping: CallbackToStateMapping, value: OnyxValue | null, matchedKey: TKey | undefined): void {
// If the mapping no longer exists then we should not send any data.
- // This means our subscriber disconnected or withOnyx wrapped component unmounted.
+ // This means our subscriber was disconnected.
if (!callbackToStateMapping[mapping.subscriptionID]) {
return;
}
- if (utils.hasWithOnyxInstance(mapping)) {
- let newData: OnyxValue = value;
-
- // If the mapping has a selector, then the component's state must only be updated with the data
- // returned by the selector.
- if (mapping.selector) {
- if (isCollectionKey(mapping.key)) {
- newData = reduceCollectionWithSelector(value as OnyxCollection, mapping.selector, mapping.withOnyxInstance.state);
- } else {
- newData = mapping.selector(value, mapping.withOnyxInstance.state);
- }
- }
-
- PerformanceUtils.logSetStateCall(mapping, null, newData, 'sendDataToConnection');
- if (isBatched) {
- batchUpdates(() => mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData));
- } else {
- mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
- }
- return;
- }
-
- // When there are no matching keys in "Onyx.connect", we pass null to "sendDataToConnection" explicitly,
- // to allow the withOnyx instance to set the value in the state initially and therefore stop the loading state once all
- // required keys have been set.
- // If we would pass undefined to setWithOnyxInstance instead, withOnyx would not set the value in the state.
- // withOnyx will internally replace null values with undefined and never pass null values to wrapped components.
// For regular callbacks, we never want to pass null values, but always just undefined if a value is not set in cache or storage.
const valueToPass = value === null ? undefined : value;
const lastValue = lastConnectionCallbackData.get(mapping.subscriptionID);
@@ -1024,34 +785,25 @@ function sendDataToConnection(mapping: Mapping, valu
* We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
* run out of storage the least recently accessed key can be removed.
*/
-function addKeyToRecentlyAccessedIfNeeded(mapping: Mapping): void {
- if (!cache.isEvictableKey(mapping.key)) {
+function addKeyToRecentlyAccessedIfNeeded(key: TKey): void {
+ if (!cache.isEvictableKey(key)) {
return;
}
// Add the key to recentKeys first (this makes it the most recent key)
- cache.addToAccessedKeys(mapping.key);
+ cache.addToAccessedKeys(key);
// Try to free some cache whenever we connect to a safe eviction key
cache.removeLeastRecentlyUsedKeys();
-
- if (utils.hasWithOnyxInstance(mapping) && !isCollectionKey(mapping.key)) {
- // All React components subscribing to a key flagged as a safe eviction key must implement the canEvict property.
- if (mapping.canEvict === undefined) {
- throw new Error(`Cannot subscribe to safe eviction key '${mapping.key}' without providing a canEvict value.`);
- }
-
- cache.addLastAccessedKey(mapping.key, isCollectionKey(mapping.key));
- }
}
/**
* Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.
*/
-function getCollectionDataAndSendAsObject(matchingKeys: CollectionKeyBase[], mapping: Mapping): void {
+function getCollectionDataAndSendAsObject(matchingKeys: CollectionKeyBase[], mapping: CallbackToStateMapping): void {
multiGet(matchingKeys).then((dataMap) => {
const data = Object.fromEntries(dataMap.entries()) as OnyxValue;
- sendDataToConnection(mapping, data, undefined, true);
+ sendDataToConnection(mapping, data, undefined);
});
}
@@ -1064,11 +816,10 @@ function getCollectionDataAndSendAsObject(matchingKeys: Co
function scheduleSubscriberUpdate(
key: TKey,
value: OnyxValue,
- previousValue: OnyxValue,
- canUpdateSubscriber: (subscriber?: Mapping) => boolean = () => true,
+ canUpdateSubscriber: (subscriber?: CallbackToStateMapping) => boolean = () => true,
): Promise {
- const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false));
- batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true));
+ const promise = Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber, true));
+ batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false));
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
}
@@ -1082,8 +833,8 @@ function scheduleNotifyCollectionSubscribers(
value: OnyxCollection,
previousValue?: OnyxCollection,
): Promise {
- const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false));
- batchUpdates(() => keysChanged(key, value, previousValue, false, true));
+ const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true));
+ batchUpdates(() => keysChanged(key, value, previousValue, false));
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
}
@@ -1091,9 +842,8 @@ function scheduleNotifyCollectionSubscribers(
* Remove a key from Onyx and update the subscribers
*/
function remove(key: TKey): Promise {
- const prevValue = cache.get(key, false) as OnyxValue;
cache.drop(key);
- scheduleSubscriberUpdate(key, undefined as OnyxValue, prevValue);
+ scheduleSubscriberUpdate(key, undefined as OnyxValue);
return Storage.removeItem(key).then(() => undefined);
}
@@ -1146,8 +896,6 @@ function evictStorageAndRetry(key: TKey, value: OnyxValue, hasChanged?: boolean): Promise {
- const prevValue = cache.get(key, false) as OnyxValue;
-
// 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) {
@@ -1156,7 +904,7 @@ function broadcastUpdate(key: TKey, value: OnyxValue
cache.addToAccessedKeys(key);
}
- return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || subscriber?.initWithStoredValues === false).then(() => undefined);
+ return scheduleSubscriberUpdate(key, value, (subscriber) => hasChanged || subscriber?.initWithStoredValues === false).then(() => undefined);
}
function hasPendingMergeForKey(key: OnyxKey): boolean {
@@ -1265,14 +1013,14 @@ function mergeInternal | undefined, TChange ex
*/
function initializeWithDefaultKeyStates(): Promise {
return Storage.multiGet(Object.keys(defaultKeyStates)).then((pairs) => {
- const existingDataAsObject = Object.fromEntries(pairs);
+ const existingDataAsObject = Object.fromEntries(pairs) as Record;
const merged = utils.fastMerge(existingDataAsObject, defaultKeyStates, {
shouldRemoveNestedNulls: true,
}).result;
cache.merge(merged ?? {});
- Object.entries(merged ?? {}).forEach(([key, value]) => keyChanged(key, value, existingDataAsObject));
+ Object.entries(merged ?? {}).forEach(([key, value]) => keyChanged(key, value));
});
}
@@ -1311,9 +1059,9 @@ function doAllCollectionItemsBelongToSameParent(
* @returns The subscription ID to use when calling `OnyxUtils.unsubscribeFromKey()`.
*/
function subscribeToKey(connectOptions: ConnectOptions): number {
- const mapping = connectOptions as Mapping;
+ const mapping = connectOptions as CallbackToStateMapping;
const subscriptionID = lastSubscriptionID++;
- callbackToStateMapping[subscriptionID] = mapping as Mapping;
+ callbackToStateMapping[subscriptionID] = mapping as CallbackToStateMapping;
callbackToStateMapping[subscriptionID].subscriptionID = subscriptionID;
// When keyChanged is called, a key is passed and the method looks through all the Subscribers in callbackToStateMapping for the matching key to get the subscriptionID
@@ -1327,7 +1075,7 @@ function subscribeToKey(connectOptions: ConnectOptions addKeyToRecentlyAccessedIfNeeded(mapping))
+ .then(() => addKeyToRecentlyAccessedIfNeeded(mapping.key))
.then(() => {
// Performance improvement
// If the mapping is connected to an onyx key that is not a collection
@@ -1359,8 +1107,7 @@ function subscribeToKey(connectOptions: ConnectOptions(connectOptions: ConnectOptions(connectOptions: ConnectOptions {
values.forEach((val, key) => {
- sendDataToConnection(mapping, val as OnyxValue, key as TKey, true);
+ sendDataToConnection(mapping, val as OnyxValue, key as TKey);
});
});
return;
}
// If we are not subscribed to a collection key then there's only a single key to send an update for.
- get(mapping.key).then((val) => sendDataToConnection(mapping, val as OnyxValue, mapping.key, true));
- return;
- }
-
- // If we have a withOnyxInstance that means a React component has subscribed via the withOnyx() HOC and we need to
- // group collection key member data into an object.
- if (utils.hasWithOnyxInstance(mapping)) {
- if (isCollectionKey(mapping.key)) {
- getCollectionDataAndSendAsObject(matchingKeys, mapping);
- return;
- }
-
- // If the subscriber is not using a collection key then we just send a single value back to the subscriber
- get(mapping.key).then((val) => sendDataToConnection(mapping, val as OnyxValue, mapping.key, true));
+ get(mapping.key).then((val) => sendDataToConnection(mapping, val as OnyxValue, mapping.key));
return;
}
- console.error('Warning: Onyx.connect() was found without a callback or withOnyxInstance');
+ console.error('Warning: Onyx.connect() was found without a callback');
});
// The subscriptionID is returned back to the caller so that it can be used to clean up the connection when it's no longer needed
diff --git a/lib/PerformanceUtils.ts b/lib/PerformanceUtils.ts
deleted file mode 100644
index 5eabc4d98..000000000
--- a/lib/PerformanceUtils.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import lodashTransform from 'lodash/transform';
-import {deepEqual} from 'fast-equals';
-import type {OnyxKey} from './types';
-import type {Mapping} from './Onyx';
-
-type UnknownObject = Record;
-
-type LogParams = {
- keyThatChanged?: string;
- difference?: unknown;
- previousValue?: unknown;
- newValue?: unknown;
-};
-
-let debugSetState = false;
-
-function setShouldDebugSetState(debug: boolean) {
- debugSetState = debug;
-}
-
-/**
- * Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so
- * that state and props updates can be optimized.
- */
-function diffObject(object: TObject, base: TBase): UnknownObject {
- return lodashTransform(object, (result: UnknownObject, value, key) => {
- if (deepEqual(value, base[key])) {
- return;
- }
-
- if (typeof value === 'object' && typeof base[key] === 'object') {
- // eslint-disable-next-line no-param-reassign
- result[key] = diffObject(value as UnknownObject, base[key] as UnknownObject);
- } else {
- // eslint-disable-next-line no-param-reassign
- result[key] = value;
- }
- });
-}
-
-/**
- * Provide insights into why a setState() call occurred by diffing the before and after values.
- */
-function logSetStateCall(mapping: Mapping, previousValue: unknown, newValue: unknown, caller: string, keyThatChanged?: string) {
- if (!debugSetState) {
- return;
- }
-
- const logParams: LogParams = {};
- if (keyThatChanged) {
- logParams.keyThatChanged = keyThatChanged;
- }
- if (newValue && previousValue && typeof newValue === 'object' && typeof previousValue === 'object') {
- logParams.difference = diffObject(previousValue as UnknownObject, newValue as UnknownObject);
- } else {
- logParams.previousValue = previousValue;
- logParams.newValue = newValue;
- }
-
- console.debug(`[Onyx-Debug] ${'displayName' in mapping && mapping.displayName} setState() called. Subscribed to key '${mapping.key}' (${caller})`, logParams);
-}
-
-export {logSetStateCall, setShouldDebugSetState};
diff --git a/lib/index.ts b/lib/index.ts
index a4bfc0ba4..ee6cb3cb7 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -20,12 +20,10 @@ import type {
import type {FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions} from './useOnyx';
import type {Connection} from './OnyxConnectionManager';
import useOnyx from './useOnyx';
-import withOnyx from './withOnyx';
-import type {WithOnyxState} from './withOnyx/types';
import type {OnyxSQLiteKeyValuePair} from './storage/providers/SQLiteProvider';
export default Onyx;
-export {useOnyx, withOnyx};
+export {useOnyx};
export type {
ConnectOptions,
CustomTypeOptions,
@@ -47,7 +45,6 @@ export type {
ResultMetadata,
Selector,
UseOnyxResult,
- WithOnyxState,
Connection,
UseOnyxOptions,
OnyxSQLiteKeyValuePair,
diff --git a/lib/types.ts b/lib/types.ts
index 07b6ddd3c..8900b7d8e 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -1,7 +1,6 @@
import type {Merge} from 'type-fest';
import type {BuiltIns} from 'type-fest/source/internal';
import type OnyxUtils from './OnyxUtils';
-import type {WithOnyxInstance, WithOnyxState} from './withOnyx/types';
import type {OnyxMethod} from './OnyxUtils';
import type {FastMergeReplaceNullPatch} from './utils';
@@ -117,71 +116,23 @@ type OnyxKey = Key | CollectionKey;
/**
* Represents a selector function type which operates based on the provided `TKey` and `ReturnType`.
*
- * A `Selector` is a function that accepts a value, the withOnyx's internal state and returns a processed value.
+ * A `Selector` is a function that accepts a value and returns a processed value.
* This type accepts two type parameters: `TKey` and `TReturnType`.
*
* The type `TKey` extends `OnyxKey` and it is the key used to access a value in `KeyValueMapping`.
* `TReturnType` is the type of the returned value from the selector function.
*/
-type Selector = (value: OnyxEntry, state?: WithOnyxState) => TReturnType;
+type Selector = (value: OnyxEntry) => TReturnType;
/**
* Represents a single Onyx entry, that can be either `TOnyxValue` or `undefined` if it doesn't exist.
- *
- * It can be used to specify data retrieved from Onyx e.g. `withOnyx` HOC mappings.
- *
- * @example
- * ```ts
- * import Onyx, {OnyxEntry, withOnyx} from 'react-native-onyx';
- *
- * type OnyxProps = {
- * userAccount: OnyxEntry;
- * };
- *
- * type Props = OnyxProps & {
- * prop1: string;
- * };
- *
- * function Component({prop1, userAccount}: Props) {
- * // ...
- * }
- *
- * export default withOnyx({
- * userAccount: {
- * key: ONYXKEYS.ACCOUNT,
- * },
- * })(Component);
- * ```
+ * It can be used to specify data retrieved from Onyx.
*/
type OnyxEntry = TOnyxValue | undefined;
/**
* Represents an Onyx collection of entries, that can be either a record of `TOnyxValue`s or `undefined` if it is empty or doesn't exist.
- *
- * It can be used to specify collection data retrieved from Onyx e.g. `withOnyx` HOC mappings.
- *
- * @example
- * ```ts
- * import Onyx, {OnyxCollection, withOnyx} from 'react-native-onyx';
- *
- * type OnyxProps = {
- * reports: OnyxCollection;
- * };
- *
- * type Props = OnyxProps & {
- * prop1: string;
- * };
- *
- * function Component({prop1, reports}: Props) {
- * // ...
- * }
- *
- * export default withOnyx({
- * reports: {
- * key: ONYXKEYS.COLLECTION.REPORT,
- * },
- * })(Component);
- * ```
+ * It can be used to specify collection data retrieved from Onyx.
*/
type OnyxCollection = OnyxEntry>;
@@ -321,31 +272,7 @@ type CollectionConnectOptions = BaseConnectOptions & {
// NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method!
type ConnectOptions = DefaultConnectOptions | CollectionConnectOptions;
-/** Represents additional `Onyx.connect()` options used inside `withOnyx()` HOC. */
-// NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method!
-type WithOnyxConnectOptions = ConnectOptions & {
- /** The `withOnyx` class instance to be internally passed. */
- withOnyxInstance: WithOnyxInstance;
-
- /** The name of the component's prop that is connected to the Onyx key. */
- statePropertyName: string;
-
- /** The component's display name. */
- displayName: string;
-
- /**
- * This will be used to subscribe to a subset of an Onyx key's data.
- * Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render
- * when the subset of data changes. Otherwise, any change of data on any property would normally
- * cause the component to re-render (and that can be expensive from a performance standpoint).
- */
- selector?: Selector;
-
- /** Determines if this key in this subscription is safe to be evicted. */
- canEvict?: boolean;
-};
-
-type Mapping = WithOnyxConnectOptions & {
+type CallbackToStateMapping = ConnectOptions & {
subscriptionID: number;
};
@@ -475,9 +402,6 @@ type InitOptions = {
*/
shouldSyncMultipleInstances?: boolean;
- /** Enables debugging setState() calls to connected components */
- debugSetState?: boolean;
-
/**
* If enabled it will use the performance API to measure the time taken by Onyx operations.
* @default false
@@ -528,7 +452,7 @@ export type {
InitOptions,
Key,
KeyValueMapping,
- Mapping,
+ CallbackToStateMapping,
NonNull,
NonUndefined,
OnyxInputKeyValueMapping,
@@ -549,7 +473,6 @@ export type {
OnyxValue,
Selector,
SetOptions,
- WithOnyxConnectOptions,
MultiMergeReplaceNullPatches,
MixedOperationsQueue,
};
diff --git a/lib/useOnyx.ts b/lib/useOnyx.ts
index 3bf238ead..016eb0f33 100644
--- a/lib/useOnyx.ts
+++ b/lib/useOnyx.ts
@@ -212,7 +212,6 @@ function useOnyx>(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...dependencies]);
- // Mimics withOnyx's checkEvictableKeys() behavior.
const checkEvictableKey = useCallback(() => {
if (options?.canEvict === undefined || !connectionRef.current) {
return;
diff --git a/lib/utils.ts b/lib/utils.ts
index 6e1561588..280568005 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,4 +1,4 @@
-import type {ConnectOptions, OnyxInput, OnyxKey} from './types';
+import type {OnyxInput, OnyxKey} from './types';
type EmptyObject = Record;
type EmptyValue = EmptyObject | null | undefined;
@@ -281,13 +281,6 @@ function omit(obj: Record, condition: string | string[]
return filterObject(obj, condition, false);
}
-/**
- * Whether the connect options has the `withOnyxInstance` property defined, that is, it's used by the `withOnyx()` HOC.
- */
-function hasWithOnyxInstance(mapping: ConnectOptions) {
- return 'withOnyxInstance' in mapping && mapping.withOnyxInstance;
-}
-
export default {
fastMerge,
isEmptyObject,
@@ -296,7 +289,6 @@ export default {
checkCompatibilityWithExistingValue,
pick,
omit,
- hasWithOnyxInstance,
ONYX_INTERNALS__REPLACE_OBJECT_MARK,
};
export type {FastMergeResult, FastMergeReplaceNullPatch, FastMergeOptions};
diff --git a/lib/withOnyx/index.tsx b/lib/withOnyx/index.tsx
deleted file mode 100644
index 4007fba20..000000000
--- a/lib/withOnyx/index.tsx
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * This is a higher order component that provides the ability to map a state property directly to
- * something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
- * will automatically change to reflect the new data.
- */
-import React from 'react';
-import OnyxUtils from '../OnyxUtils';
-import * as Str from '../Str';
-import type {GenericFunction, Mapping, OnyxKey, WithOnyxConnectOptions} from '../types';
-import utils from '../utils';
-import type {MapOnyxToState, WithOnyxInstance, WithOnyxMapping, WithOnyxProps, WithOnyxState} from './types';
-import cache from '../OnyxCache';
-import type {Connection} from '../OnyxConnectionManager';
-import connectionManager from '../OnyxConnectionManager';
-
-// 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
-// if a key has changed, it's a good idea to skip looking at these properties since they would have unexpected results.
-const mappingPropertiesToIgnoreChangesTo: Array = ['allowStaleData'];
-
-/**
- * Returns the display name of a component
- */
-function getDisplayName(component: React.ComponentType): string {
- return component.displayName || component.name || 'Component';
-}
-
-/**
- * Removes all the keys from state that are unrelated to the onyx data being mapped to the component.
- *
- * @param state of the component
- * @param onyxToStateMapping the object holding all of the mapping configuration for the component
- */
-function getOnyxDataFromState(state: WithOnyxState, onyxToStateMapping: MapOnyxToState) {
- return utils.pick(state, Object.keys(onyxToStateMapping)) as Partial>;
-}
-
-/**
- * Utility function to return the properly typed entries of the `withOnyx` mapping object.
- */
-function mapOnyxToStateEntries(mapOnyxToState: MapOnyxToState) {
- return Object.entries(mapOnyxToState) as Array<[keyof TOnyxProps, WithOnyxMapping]>;
-}
-
-/**
- * @deprecated Use `useOnyx` instead of `withOnyx` whenever possible.
- *
- * This is a higher order component that provides the ability to map a state property directly to
- * something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
- * will automatically change to reflect the new data.
- */
-export default function (
- mapOnyxToState: MapOnyxToState,
- shouldDelayUpdates = false,
-): (component: React.ComponentType) => React.ComponentType> {
- // A list of keys that must be present in tempState before we can render the WrappedComponent
- const requiredKeysForInit = Object.keys(
- utils.omit(mapOnyxToState, ([, options]) => (options as MapOnyxToState[keyof TOnyxProps]).initWithStoredValues === false),
- );
-
- return (WrappedComponent: React.ComponentType): React.ComponentType> => {
- const displayName = getDisplayName(WrappedComponent);
-
- class withOnyx extends React.Component, WithOnyxState> {
- // eslint-disable-next-line react/static-property-placement
- static displayName: string;
-
- pendingSetStates: Array | ((state: WithOnyxState) => WithOnyxState | null)> = [];
-
- shouldDelayUpdates: boolean;
-
- activeConnections: Record;
-
- tempState: WithOnyxState | undefined;
-
- constructor(props: WithOnyxProps) {
- super(props);
-
- this.shouldDelayUpdates = shouldDelayUpdates;
- this.setWithOnyxState = this.setWithOnyxState.bind(this);
- this.flushPendingSetStates = this.flushPendingSetStates.bind(this);
-
- // This stores all the Onyx connections to be used when the component unmounts so everything can be
- // disconnected. It is a key value store with the format {[mapping.key]: connection metadata object}.
- this.activeConnections = {};
-
- const cachedState = mapOnyxToStateEntries(mapOnyxToState).reduce>((resultObj, [propName, mapping]) => {
- const key = Str.result(mapping.key as GenericFunction, props);
- const value = OnyxUtils.tryGetCachedValue(key, mapping as Mapping);
- const hasCacheForKey = cache.hasCacheForKey(key);
-
- /**
- * If we have a pending merge for a key it could mean that data is being set via Onyx.merge() and someone expects a component to have this data immediately.
- *
- * @example
- *
- * Onyx.merge('report_123', value);
- * Navigation.navigate(route); // Where "route" expects the "value" to be available immediately once rendered.
- *
- * 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).
- * So, we won't use the cache optimization here as it will lead us to arbitrarily defer various actions in the application code.
- */
- const hasPendingMergeForKey = OnyxUtils.hasPendingMergeForKey(key);
- const hasValueInCache = hasCacheForKey || value !== undefined;
- const shouldSetState = mapping.initWithStoredValues !== false && ((hasValueInCache && !hasPendingMergeForKey) || !!mapping.allowStaleData);
-
- if (shouldSetState) {
- // eslint-disable-next-line no-param-reassign
- resultObj[propName] = value as WithOnyxState[keyof TOnyxProps];
- }
-
- return resultObj;
- }, {} as WithOnyxState);
-
- // If we have all the data we need, then we can render the component immediately
- cachedState.loading = Object.keys(cachedState).length < requiredKeysForInit.length;
-
- // Object holding the temporary initial state for the component while we load the various Onyx keys
- this.tempState = cachedState;
-
- this.state = cachedState;
- }
-
- componentDidMount() {
- const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
-
- // Subscribe each of the state properties to the proper Onyx key
- mapOnyxToStateEntries(mapOnyxToState).forEach(([propName, mapping]) => {
- if (mappingPropertiesToIgnoreChangesTo.includes(propName)) {
- return;
- }
-
- const key = Str.result(mapping.key as GenericFunction, {...this.props, ...onyxDataFromState});
- this.connectMappingToOnyx(mapping, propName, key);
- });
-
- this.checkEvictableKeys();
- }
-
- componentDidUpdate(prevProps: WithOnyxProps, prevState: WithOnyxState) {
- // The whole purpose of this method is to check to see if a key that is subscribed to Onyx has changed, and then Onyx needs to be disconnected from the old
- // key and connected to the new key.
- // For example, a key could change if KeyB depends on data loading from Onyx for KeyA.
- const isFirstTimeUpdatingAfterLoading = prevState.loading && !this.state.loading;
- const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
- const prevOnyxDataFromState = getOnyxDataFromState(prevState, mapOnyxToState);
-
- mapOnyxToStateEntries(mapOnyxToState).forEach(([propName, mapping]) => {
- // Some properties can be ignored because they aren't related to onyx keys and they will never change
- if (mappingPropertiesToIgnoreChangesTo.includes(propName)) {
- return;
- }
-
- // The previous key comes from either:
- // 1) The initial key that was connected to (ie. set from `componentDidMount()`)
- // 2) The updated props which caused `componentDidUpdate()` to run
- // The first case cannot be used all the time because of race conditions where `componentDidUpdate()` can be triggered before connectingMappingToOnyx() is done
- // (eg. if a user switches chats really quickly). In this case, it's much more stable to always look at the changes to prevProp and prevState to derive the key.
- // The second case cannot be used all the time because the onyx data doesn't change the first time that `componentDidUpdate()` runs after loading. In this case,
- // the `mapping.previousKey` must be used for the comparison or else this logic never detects that onyx data could have changed during the loading process.
- const previousKey = isFirstTimeUpdatingAfterLoading ? mapping.previousKey : Str.result(mapping.key as GenericFunction, {...prevProps, ...prevOnyxDataFromState});
- const newKey = Str.result(mapping.key as GenericFunction, {...this.props, ...onyxDataFromState});
- if (previousKey !== newKey) {
- connectionManager.disconnect(this.activeConnections[previousKey]);
- delete this.activeConnections[previousKey];
- this.connectMappingToOnyx(mapping, propName, newKey);
- }
- });
-
- this.checkEvictableKeys();
- }
-
- componentWillUnmount() {
- // Disconnect everything from Onyx
- mapOnyxToStateEntries(mapOnyxToState).forEach(([, mapping]) => {
- const key = Str.result(mapping.key as GenericFunction, {...this.props, ...getOnyxDataFromState(this.state, mapOnyxToState)});
- connectionManager.disconnect(this.activeConnections[key]);
- });
- }
-
- setStateProxy(modifier: WithOnyxState | ((state: WithOnyxState) => WithOnyxState | null)) {
- if (this.shouldDelayUpdates) {
- this.pendingSetStates.push(modifier);
- } else {
- this.setState(modifier);
- }
- }
-
- /**
- * This method is used by the internal raw Onyx `sendDataToConnection`, it is designed to prevent unnecessary renders while a component
- * still in a "loading" (read "mounting") state. The temporary initial state is saved to the HOC instance and setState()
- * only called once all the necessary data has been collected.
- *
- * There is however the possibility the component could have been updated by a call to setState()
- * before the data was "initially" collected. A race condition.
- * For example some update happened on some key, while onyx was still gathering the initial hydration data.
- * This update is disptached directly to setStateProxy and therefore the component has the most up-to-date data
- *
- * This is a design flaw in Onyx itself as dispatching updates before initial hydration is not a correct event flow.
- * We however need to workaround this issue in the HOC. The addition of initialValue makes things even more complex,
- * since you cannot be really sure if the component has been updated before or after the initial hydration. Therefore if
- * initialValue is there, we just check if the update is different than that and then try to handle it as best as we can.
- */
- setWithOnyxState(statePropertyName: T, val: WithOnyxState[T]) {
- const prevVal = this.state[statePropertyName];
-
- // If the component is not loading (read "mounting"), then we can just update the state
- // There is a small race condition.
- // When calling setWithOnyxState we delete the tempState object that is used to hold temporary state updates while the HOC is gathering data.
- // However the loading flag is only set on the setState callback down below. setState however is an async operation that is also batched,
- // therefore there is a small window of time where the loading flag is not false but the tempState is already gone
- // (while the update is queued and waiting to be applied).
- // This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
- if (!this.state.loading || !this.tempState) {
- // Performance optimization, do not trigger update with same values
- if (prevVal === val || (utils.isEmptyObject(prevVal) && utils.isEmptyObject(val))) {
- return;
- }
-
- const valueWithoutNull = val === null ? undefined : val;
-
- this.setStateProxy({[statePropertyName]: valueWithoutNull} as WithOnyxState);
- return;
- }
-
- this.tempState[statePropertyName] = val;
-
- // If some key does not have a value yet, do not update the state yet
- const tempStateIsMissingKey = requiredKeysForInit.some((key) => !(key in (this.tempState ?? {})));
-
- if (tempStateIsMissingKey) {
- return;
- }
-
- const stateUpdate = {...this.tempState};
- delete this.tempState;
-
- // Full of hacky workarounds to prevent the race condition described above.
- this.setState((prevState) => {
- const finalState = Object.keys(stateUpdate).reduce>((result, _key) => {
- const key = _key as keyof TOnyxProps;
-
- if (key === 'loading') {
- return result;
- }
-
- // if value is already there then we can discard the value we are trying to hydrate
- if (prevState[key] !== undefined && prevState[key] !== null) {
- // eslint-disable-next-line no-param-reassign
- result[key] = prevState[key];
- } else if (stateUpdate[key] !== null) {
- // eslint-disable-next-line no-param-reassign
- result[key] = stateUpdate[key];
- }
-
- return result;
- }, {} as WithOnyxState);
-
- finalState.loading = false;
-
- return finalState;
- });
- }
-
- /**
- * Makes sure each Onyx key we requested has been set to state with a value of some kind.
- * We are doing this so that the wrapped component will only render when all the data
- * it needs is available to it.
- */
- checkEvictableKeys() {
- // We will add this key to our list of recently accessed keys
- // if the canEvict function returns true. This is necessary criteria
- // we MUST use to specify if a key can be removed or not.
- mapOnyxToStateEntries(mapOnyxToState).forEach(([, mapping]) => {
- if (mapping.canEvict === undefined) {
- return;
- }
-
- const canEvict = !!Str.result(mapping.canEvict as GenericFunction, this.props);
- const key = Str.result(mapping.key as GenericFunction, this.props);
-
- if (!cache.isEvictableKey(key)) {
- throw new Error(`canEvict can't be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to Onyx.init({evictableKeys: []}).`);
- }
-
- if (canEvict) {
- connectionManager.removeFromEvictionBlockList(this.activeConnections[key]);
- } else {
- connectionManager.addToEvictionBlockList(this.activeConnections[key]);
- }
- });
- }
-
- /**
- * Takes a single mapping and binds the state of the component to the store
- *
- * @param mapping.key key to connect to. can be a string or a
- * function that takes this.props as an argument and returns a string
- * @param statePropertyName the name of the state property that Onyx will add the data to
- * @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
- * component
- * @param key to connect to Onyx with
- */
- connectMappingToOnyx(mapping: MapOnyxToState[keyof TOnyxProps], statePropertyName: keyof TOnyxProps, key: OnyxKey) {
- const onyxMapping = mapOnyxToState[statePropertyName] as WithOnyxMapping;
-
- // Remember what the previous key was so that key changes can be detected when data is being loaded from Onyx. This will allow
- // dependent keys to finish loading their data.
- // eslint-disable-next-line no-param-reassign
- onyxMapping.previousKey = key;
-
- // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
- this.activeConnections[key] = connectionManager.connect({
- ...mapping,
- key,
- statePropertyName: statePropertyName as string,
- withOnyxInstance: this as unknown as WithOnyxInstance,
- displayName,
- } as WithOnyxConnectOptions);
- }
-
- flushPendingSetStates() {
- if (!this.shouldDelayUpdates) {
- return;
- }
-
- this.shouldDelayUpdates = false;
-
- this.pendingSetStates.forEach((modifier) => {
- this.setState(modifier as WithOnyxState);
- });
-
- this.pendingSetStates = [];
- }
-
- render() {
- // Remove any null values so that React replaces them with default props
- const propsToPass = utils.omit(this.props as Omit, ([, propValue]) => propValue === null);
-
- if (this.state.loading) {
- return null;
- }
-
- // Remove any internal state properties used by withOnyx
- // that should not be passed to a wrapped component
- const stateToPass = utils.omit(this.state as WithOnyxState, ([stateKey, stateValue]) => stateKey === 'loading' || stateValue === null);
-
- // Spreading props and state is necessary in an HOC where the data cannot be predicted
- return (
-
- );
- }
- }
-
- withOnyx.displayName = `withOnyx(${displayName})`;
-
- return React.forwardRef((props, ref) => {
- const Component = withOnyx;
- return (
-
- );
- });
- };
-}
diff --git a/lib/withOnyx/types.ts b/lib/withOnyx/types.ts
deleted file mode 100644
index 0fa84df68..000000000
--- a/lib/withOnyx/types.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import type {ForwardedRef} from 'react';
-import type {IsEqual} from 'type-fest';
-import type {CollectionKeyBase, ExtractOnyxCollectionValue, KeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector} from '../types';
-
-/**
- * Represents the base mapping options between an Onyx key and the component's prop.
- */
-type BaseMapping = {
- canEvict?: boolean | ((props: Omit) => boolean);
- initWithStoredValues?: boolean;
- allowStaleData?: boolean;
-};
-
-/**
- * Represents the string / function `key` mapping option between an Onyx key and the component's prop.
- *
- * If `key` is `string`, the type of the Onyx value that is associated with `key` must match with the type of the component's prop,
- * otherwise an error will be thrown.
- *
- * If `key` is `function`, the return type of `key` function must be a valid Onyx key and the type of the Onyx value associated
- * with `key` must match with the type of the component's prop, otherwise an error will be thrown.
- *
- * @example
- * ```ts
- * // Onyx prop with `string` key
- * onyxProp: {
- * key: ONYXKEYS.ACCOUNT,
- * },
- *
- * // Onyx prop with `function` key
- * onyxProp: {
- * key: ({reportId}) => ONYXKEYS.ACCOUNT,
- * },
- * ```
- */
-type BaseMappingKey = IsEqual extends true
- ? {
- key: TOnyxKey | ((props: Omit & Partial) => TOnyxKey);
- }
- : never;
-
-/**
- * Represents the string `key` and `selector` mapping options between an Onyx key and the component's prop.
- *
- * The function signature and return type of `selector` must match with the type of the component's prop,
- * otherwise an error will be thrown.
- *
- * @example
- * ```ts
- * // Onyx prop with `string` key and selector
- * onyxProp: {
- * key: ONYXKEYS.ACCOUNT,
- * selector: (value: Account | null): string => value?.id ?? '',
- * },
- * ```
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-type BaseMappingStringKeyAndSelector = {
- key: TOnyxKey;
- selector: Selector;
-};
-
-/**
- * Represents the function `key` and `selector` mapping options between an Onyx key and the component's prop.
- *
- * The function signature and return type of `selector` must match with the type of the component's prop,
- * otherwise an error will be thrown.
- *
- * @example
- * ```ts
- * // Onyx prop with `function` key and selector
- * onyxProp: {
- * key: ({reportId}) => ONYXKEYS.ACCOUNT,
- * selector: (value: Account | null) => value?.id ?? '',
- * },
- * ```
- */
-type BaseMappingFunctionKeyAndSelector = {
- key: (props: Omit & Partial) => TOnyxKey;
- selector: Selector;
-};
-
-/**
- * Represents the mapping options between an Onyx key and the component's prop with all its possibilities.
- */
-type Mapping = BaseMapping &
- (
- | BaseMappingKey>
- | BaseMappingStringKeyAndSelector
- | BaseMappingFunctionKeyAndSelector
- );
-
-/**
- * Represents a superset of `Mapping` type with internal properties included.
- */
-type WithOnyxMapping = Mapping & {
- subscriptionID: number;
- previousKey?: OnyxKey;
-};
-
-/**
- * Represents the mapping options between an Onyx collection key without suffix and the component's prop with all its possibilities.
- */
-type CollectionMapping = BaseMapping &
- (
- | BaseMappingKey>
- | BaseMappingStringKeyAndSelector, TOnyxKey>
- | BaseMappingFunctionKeyAndSelector, TOnyxKey>
- );
-
-/**
- * Represents an union type of all the possible Onyx key mappings.
- * Each `OnyxPropMapping` will be associated with its respective Onyx key, ensuring different type-safety for each object.
- */
-type OnyxPropMapping = {
- [TOnyxKey in OnyxKey]: Mapping;
-}[OnyxKey];
-
-/**
- * Represents an union type of all the possible Onyx collection keys without suffix mappings.
- * Each `OnyxPropCollectionMapping` will be associated with its respective Onyx key, ensuring different type-safety for each object.
- */
-type OnyxPropCollectionMapping = {
- [TOnyxKey in CollectionKeyBase]: CollectionMapping;
-}[CollectionKeyBase];
-
-/**
- * Represents an Onyx mapping object that connects Onyx keys to component's props.
- */
-type MapOnyxToState = {
- [TOnyxProp in keyof TOnyxProps]: OnyxPropMapping | OnyxPropCollectionMapping;
-};
-
-/**
- * Represents the `withOnyx` internal component props.
- */
-type WithOnyxProps = Omit & {forwardedRef?: ForwardedRef};
-
-/**
- * Represents the `withOnyx` internal component state.
- */
-type WithOnyxState = TOnyxProps & {
- loading: boolean;
-};
-
-/**
- * Represents the `withOnyx` internal component instance.
- */
-type WithOnyxInstance = React.Component> & {
- setStateProxy: (modifier: Record> | ((state: Record>) => OnyxValue)) => void;
- setWithOnyxState: (statePropertyName: OnyxKey, value: OnyxValue) => void;
-};
-
-export type {WithOnyxMapping, MapOnyxToState, WithOnyxProps, WithOnyxInstance, WithOnyxState};
diff --git a/package-lock.json b/package-lock.json
index 678986ff4..f6c609e3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-native-onyx",
- "version": "2.0.141",
+ "version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "react-native-onyx",
- "version": "2.0.141",
+ "version": "3.0.0",
"license": "MIT",
"dependencies": {
"ascii-table": "0.0.9",
diff --git a/package.json b/package.json
index 5a965ffc0..32faf9158 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-onyx",
- "version": "2.0.141",
+ "version": "3.0.0",
"author": "Expensify, Inc.",
"homepage": "https://expensify.com",
"description": "State management for React Native",
diff --git a/tests/components/ViewWithCollections.tsx b/tests/components/ViewWithCollections.tsx
deleted file mode 100644
index fd15f5e90..000000000
--- a/tests/components/ViewWithCollections.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, {forwardRef, useImperativeHandle} from 'react';
-import {View, Text} from 'react-native';
-import utils from '../../lib/utils';
-
-type ViewWithCollectionsProps = {
- collections: Record;
- testObject: {isDefaultProp: boolean};
- onRender: (props: ViewWithCollectionsProps) => void;
- markReadyForHydration: () => void;
-} & Record;
-
-function ViewWithCollections(
- {collections = {}, testObject = {isDefaultProp: true}, onRender, markReadyForHydration, ...rest}: ViewWithCollectionsProps,
- ref: React.Ref<{markReadyForHydration: () => void}>,
-) {
- useImperativeHandle(ref, () => ({
- markReadyForHydration,
- }));
-
- onRender?.({collections, testObject, onRender, markReadyForHydration, ...rest});
- if (utils.isEmptyObject(collections)) {
- return empty;
- }
-
- return (
-
- {Object.values(collections).map((collection, i) => (
- // eslint-disable-next-line react/no-array-index-key
- {collection?.ID}
- ))}
-
- );
-}
-
-export default forwardRef(ViewWithCollections);
-export type {ViewWithCollectionsProps};
diff --git a/tests/components/ViewWithObject.tsx b/tests/components/ViewWithObject.tsx
deleted file mode 100644
index 073c5c522..000000000
--- a/tests/components/ViewWithObject.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import {Text, View} from 'react-native';
-
-type ViewWithObjectProps = {
- onRender?: () => void;
-} & Record;
-
-function ViewWithObject({onRender, ...rest}: ViewWithObjectProps) {
- onRender?.();
-
- return (
-
- {JSON.stringify(rest)}
-
- );
-}
-
-export default ViewWithObject;
-export type {ViewWithObjectProps};
diff --git a/tests/components/ViewWithText.tsx b/tests/components/ViewWithText.tsx
deleted file mode 100644
index 1a0dc95f3..000000000
--- a/tests/components/ViewWithText.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import {View, Text} from 'react-native';
-
-type ViewWithTextOnyxProps = {text: unknown};
-type ViewWithTextProps = ViewWithTextOnyxProps & {
- // eslint-disable-next-line react/no-unused-prop-types -- it's used in withOnyx in the tests
- collectionID?: string;
- onRender?: () => void;
-};
-
-function ViewWithText({onRender, text}: ViewWithTextProps) {
- onRender?.();
-
- return (
-
- {(text as string) || 'null'}
-
- );
-}
-
-export default ViewWithText;
-export type {ViewWithTextProps, ViewWithTextOnyxProps};
diff --git a/tests/perf-test/OnyxUtils.perf-test.ts b/tests/perf-test/OnyxUtils.perf-test.ts
index 399b53ba3..5a817b859 100644
--- a/tests/perf-test/OnyxUtils.perf-test.ts
+++ b/tests/perf-test/OnyxUtils.perf-test.ts
@@ -1,16 +1,15 @@
import {measureAsyncFunction, measureFunction} from 'reassure';
import {randBoolean} from '@ngneat/falso';
import createRandomReportAction, {getRandomReportActions} from '../utils/collections/reportActions';
-import type {OnyxKey, Selector} from '../../lib';
+import type {Selector} from '../../lib';
import Onyx from '../../lib';
import StorageMock from '../../lib/storage';
import OnyxCache from '../../lib/OnyxCache';
import OnyxUtils, {clearOnyxUtilsInternals} from '../../lib/OnyxUtils';
import type GenericCollection from '../utils/GenericCollection';
-import type {Mapping, OnyxUpdate} from '../../lib/Onyx';
+import type {OnyxUpdate} from '../../lib/Onyx';
import createDeferredTask from '../../lib/createDeferredTask';
import type {OnyxInputKeyValueMapping} from '../../lib/types';
-import generateEmptyWithOnyxInstance from '../utils/generateEmptyWithOnyxInstance';
const ONYXKEYS = {
TEST_KEY: 'test',
@@ -198,7 +197,7 @@ describe('OnyxUtils', () => {
...getRandomReportActions(collectionKey),
};
- test('one call passing normal key without selector', async () => {
+ test('one call passing normal key', async () => {
await measureFunction(() => OnyxUtils.tryGetCachedValue(key), {
beforeEach: async () => {
await Onyx.set(key, reportAction);
@@ -207,22 +206,7 @@ describe('OnyxUtils', () => {
});
});
- test('one call passing normal key with selector', async () => {
- await measureFunction(
- () =>
- OnyxUtils.tryGetCachedValue(key, {
- selector: generateTestSelector(),
- }),
- {
- beforeEach: async () => {
- await Onyx.set(key, reportAction);
- },
- afterEach: clearOnyxAfterEachMeasure,
- },
- );
- });
-
- test('one call passing collection key without selector', async () => {
+ test('one call passing collection key', async () => {
await measureFunction(() => OnyxUtils.tryGetCachedValue(collectionKey), {
beforeEach: async () => {
await Onyx.multiSet(collections);
@@ -230,21 +214,6 @@ describe('OnyxUtils', () => {
afterEach: clearOnyxAfterEachMeasure,
});
});
-
- test('one call passing collection key with selector', async () => {
- await measureFunction(
- () =>
- OnyxUtils.tryGetCachedValue(collectionKey, {
- selector: generateTestSelector(),
- }),
- {
- beforeEach: async () => {
- await Onyx.multiSet(collections);
- },
- afterEach: clearOnyxAfterEachMeasure,
- },
- );
- });
});
describe('removeLastAccessedKey', () => {
@@ -359,7 +328,7 @@ describe('OnyxUtils', () => {
const previousReportAction = mockedReportActionsMap[`${collectionKey}0`];
const changedReportAction = createRandomReportAction(Number(previousReportAction.reportActionID));
- await measureFunction(() => OnyxUtils.keyChanged(key, changedReportAction, previousReportAction), {
+ await measureFunction(() => OnyxUtils.keyChanged(key, changedReportAction), {
beforeEach: async () => {
await Onyx.set(key, previousReportAction);
for (let i = 0; i < 10000; i++) {
@@ -379,54 +348,19 @@ describe('OnyxUtils', () => {
});
describe('sendDataToConnection', () => {
- test('one call with 10k heavy objects passing to a regular subscriber', async () => {
- let subscriptionID = -1;
-
- await measureFunction(
- () =>
- OnyxUtils.sendDataToConnection(
- // @ts-expect-error we just need to pass these properties
- {
- key: collectionKey,
- subscriptionID,
- callback: jest.fn(),
- } as Mapping,
- mockedReportActionsMap,
- undefined,
- false,
- ),
- {
- beforeEach: async () => {
- await Onyx.multiSet(mockedReportActionsMap);
- subscriptionID = OnyxUtils.subscribeToKey({key: collectionKey, callback: jest.fn(), initWithStoredValues: false});
- },
- afterEach: async () => {
- if (subscriptionID) {
- OnyxUtils.unsubscribeFromKey(subscriptionID);
- }
- await clearOnyxAfterEachMeasure();
- },
- },
- );
- });
-
- test('one call with 10k heavy objects passing to a withOnyx subscriber with selector', async () => {
+ test('one call with 10k heavy objects', async () => {
let subscriptionID = -1;
await measureFunction(
() =>
OnyxUtils.sendDataToConnection(
- // @ts-expect-error we just need to pass these properties
{
key: collectionKey,
subscriptionID,
callback: jest.fn(),
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- selector: generateTestSelector(),
- } as Mapping,
+ },
mockedReportActionsMap,
undefined,
- false,
),
{
beforeEach: async () => {
@@ -464,17 +398,13 @@ describe('OnyxUtils', () => {
initWithStoredValues: false,
});
- OnyxUtils.getCollectionDataAndSendAsObject(
- mockedReportActionsKeys,
- // @ts-expect-error we just need to pass these properties
- {
- key: collectionKey,
- subscriptionID,
- callback: () => {
- callback.resolve?.();
- },
- } as Mapping,
- );
+ OnyxUtils.getCollectionDataAndSendAsObject(mockedReportActionsKeys, {
+ key: collectionKey,
+ subscriptionID,
+ callback: () => {
+ callback.resolve?.();
+ },
+ });
return callback.promise;
},
@@ -501,28 +431,25 @@ describe('OnyxUtils', () => {
Object.entries(mockedReportActionsMap).map(([k, v]) => [k, createRandomReportAction(Number(v.reportActionID))] as const),
) as GenericCollection;
- await measureAsyncFunction(
- () => Promise.all(Object.entries(changedReportActions).map(([key, value]) => OnyxUtils.scheduleSubscriberUpdate(key, value, mockedReportActionsMap[key]))),
- {
- beforeEach: async () => {
- await Onyx.multiSet(mockedReportActionsMap);
- mockedReportActionsKeys.forEach((key) => {
- const id = OnyxUtils.subscribeToKey({key, callback: jest.fn(), initWithStoredValues: false});
- subscriptionMap.set(key, id);
- });
- },
- afterEach: async () => {
- mockedReportActionsKeys.forEach((key) => {
- const id = subscriptionMap.get(key);
- if (id) {
- OnyxUtils.unsubscribeFromKey(id);
- }
- });
- subscriptionMap.clear();
- await clearOnyxAfterEachMeasure();
- },
+ await measureAsyncFunction(() => Promise.all(Object.entries(changedReportActions).map(([key, value]) => OnyxUtils.scheduleSubscriberUpdate(key, value))), {
+ beforeEach: async () => {
+ await Onyx.multiSet(mockedReportActionsMap);
+ mockedReportActionsKeys.forEach((key) => {
+ const id = OnyxUtils.subscribeToKey({key, callback: jest.fn(), initWithStoredValues: false});
+ subscriptionMap.set(key, id);
+ });
},
- );
+ afterEach: async () => {
+ mockedReportActionsKeys.forEach((key) => {
+ const id = subscriptionMap.get(key);
+ if (id) {
+ OnyxUtils.unsubscribeFromKey(id);
+ }
+ });
+ subscriptionMap.clear();
+ await clearOnyxAfterEachMeasure();
+ },
+ });
});
});
@@ -861,28 +788,19 @@ describe('OnyxUtils', () => {
test('one call', async () => {
const key = `${ONYXKEYS.COLLECTION.EVICTABLE_TEST_KEY}0`;
- await measureFunction(
- () =>
- // @ts-expect-error we just need to pass these properties
- OnyxUtils.addKeyToRecentlyAccessedIfNeeded({
- key,
- canEvict: true,
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- }),
- {
- afterEach: async () => {
- OnyxCache.removeLastAccessedKey(key);
- await clearOnyxAfterEachMeasure();
- },
+ await measureFunction(() => OnyxUtils.addKeyToRecentlyAccessedIfNeeded(key), {
+ afterEach: async () => {
+ OnyxCache.removeLastAccessedKey(key);
+ await clearOnyxAfterEachMeasure();
},
- );
+ });
});
});
describe('reduceCollectionWithSelector', () => {
test('one call with 10k heavy objects', async () => {
const selector = generateTestSelector();
- await measureFunction(() => OnyxUtils.reduceCollectionWithSelector(mockedReportActionsMap, selector, undefined));
+ await measureFunction(() => OnyxUtils.reduceCollectionWithSelector(mockedReportActionsMap, selector));
});
});
diff --git a/tests/perf-test/utils.perf-test.ts b/tests/perf-test/utils.perf-test.ts
index ae572b951..fe9df4b32 100644
--- a/tests/perf-test/utils.perf-test.ts
+++ b/tests/perf-test/utils.perf-test.ts
@@ -1,9 +1,7 @@
import {measureFunction} from 'reassure';
import Onyx from '../../lib';
-import type {OnyxKey, WithOnyxConnectOptions} from '../../lib/types';
import utils from '../../lib/utils';
import createRandomReportAction, {getRandomReportActions} from '../utils/collections/reportActions';
-import generateEmptyWithOnyxInstance from '../utils/generateEmptyWithOnyxInstance';
const ONYXKEYS = {
COLLECTION: {
@@ -93,16 +91,4 @@ describe('utils', () => {
await measureFunction(() => utils.omit(reportAction, (entry) => typeof entry[1] === 'boolean'));
});
});
-
- describe('hasWithOnyxInstance', () => {
- test('one call', async () => {
- const options: WithOnyxConnectOptions = {
- displayName: '',
- key: '',
- statePropertyName: '',
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- };
- await measureFunction(() => utils.hasWithOnyxInstance(options));
- });
- });
});
diff --git a/tests/unit/OnyxConnectionManagerTest.ts b/tests/unit/OnyxConnectionManagerTest.ts
index c20e75611..1014eef8f 100644
--- a/tests/unit/OnyxConnectionManagerTest.ts
+++ b/tests/unit/OnyxConnectionManagerTest.ts
@@ -5,10 +5,8 @@ import type {Connection} from '../../lib/OnyxConnectionManager';
import connectionManager from '../../lib/OnyxConnectionManager';
import OnyxCache from '../../lib/OnyxCache';
import StorageMock from '../../lib/storage';
-import type {OnyxKey, WithOnyxConnectOptions} from '../../lib/types';
import type GenericCollection from '../utils/GenericCollection';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
-import generateEmptyWithOnyxInstance from '../utils/generateEmptyWithOnyxInstance';
// We need access to some internal properties of `connectionManager` during the tests but they are private,
// so this workaround allows us to have access to them.
@@ -60,14 +58,6 @@ describe('OnyxConnectionManager', () => {
const connectionID3 = generateConnectionID({key: ONYXKEYS.TEST_KEY, initWithStoredValues: false});
expect(connectionID3.startsWith(`onyxKey=${ONYXKEYS.TEST_KEY},initWithStoredValues=false,waitForCollectionCallback=false,sessionID=${getSessionID()},uniqueID=`)).toBeTruthy();
-
- const connectionID4 = generateConnectionID({
- key: ONYXKEYS.TEST_KEY,
- displayName: 'Component1',
- statePropertyName: 'prop1',
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- } as WithOnyxConnectOptions);
- expect(connectionID4.startsWith(`onyxKey=${ONYXKEYS.TEST_KEY},initWithStoredValues=true,waitForCollectionCallback=false,sessionID=${getSessionID()},uniqueID=`)).toBeTruthy();
});
it('should generate an unique connection ID if the session ID is changed', async () => {
@@ -369,64 +359,6 @@ describe('OnyxConnectionManager', () => {
expect(callback3).toHaveBeenCalledTimes(1);
expect(callback3).toHaveBeenCalledWith('test2', ONYXKEYS.TEST_KEY);
});
-
- describe('withOnyx', () => {
- it('should connect to a key two times with withOnyx and create two connections instead of one', async () => {
- await StorageMock.setItem(ONYXKEYS.TEST_KEY, 'test');
-
- const connection1 = connectionManager.connect({
- key: ONYXKEYS.TEST_KEY,
- displayName: 'Component1',
- statePropertyName: 'prop1',
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- } as WithOnyxConnectOptions);
-
- const connection2 = connectionManager.connect({
- key: ONYXKEYS.TEST_KEY,
- displayName: 'Component2',
- statePropertyName: 'prop2',
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- } as WithOnyxConnectOptions);
-
- await act(async () => waitForPromisesToResolve());
-
- expect(connection1.id).not.toEqual(connection2.id);
- expect(connectionsMap.size).toEqual(2);
- expect(connectionsMap.has(connection1.id)).toBeTruthy();
- expect(connectionsMap.has(connection2.id)).toBeTruthy();
-
- connectionManager.disconnect(connection1);
- connectionManager.disconnect(connection2);
-
- expect(connectionsMap.size).toEqual(0);
- });
-
- it('should connect to a key directly, connect again with withOnyx but create another connection instead of reusing the first one', async () => {
- await StorageMock.setItem(ONYXKEYS.TEST_KEY, 'test');
-
- const callback1 = jest.fn();
- const connection1 = connectionManager.connect({key: ONYXKEYS.TEST_KEY, callback: callback1});
-
- await act(async () => waitForPromisesToResolve());
-
- const connection2 = connectionManager.connect({
- key: ONYXKEYS.TEST_KEY,
- displayName: 'Component2',
- statePropertyName: 'prop2',
- withOnyxInstance: generateEmptyWithOnyxInstance(),
- } as WithOnyxConnectOptions);
-
- expect(connection1.id).not.toEqual(connection2.id);
- expect(connectionsMap.size).toEqual(2);
- expect(connectionsMap.has(connection1.id)).toBeTruthy();
- expect(connectionsMap.has(connection2.id)).toBeTruthy();
-
- connectionManager.disconnect(connection1);
- connectionManager.disconnect(connection2);
-
- expect(connectionsMap.size).toEqual(0);
- });
- });
});
describe('disconnectAll', () => {
diff --git a/tests/unit/onyxCacheTest.tsx b/tests/unit/onyxCacheTest.tsx
index f72d24919..26f9976c9 100644
--- a/tests/unit/onyxCacheTest.tsx
+++ b/tests/unit/onyxCacheTest.tsx
@@ -1,33 +1,15 @@
-import React from 'react';
-import {render, configure as configureRNTL, resetToDefaults as resetRNTLToDefaults} from '@testing-library/react-native';
-import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
-import type {ViewWithTextOnyxProps, ViewWithTextProps} from '../components/ViewWithText';
-import ViewWithText from '../components/ViewWithText';
-import type {ViewWithCollectionsProps} from '../components/ViewWithCollections';
-import ViewWithCollections from '../components/ViewWithCollections';
+import type OnyxInstance from '../../lib/Onyx';
import type OnyxCache from '../../lib/OnyxCache';
+import type {CacheTask} from '../../lib/OnyxCache';
+import type {Connection} from '../../lib/OnyxConnectionManager';
import type MockedStorage from '../../lib/storage/__mocks__';
-import type OnyxInstance from '../../lib/Onyx';
-import type withOnyxType from '../../lib/withOnyx';
import type {InitOptions} from '../../lib/types';
import generateRange from '../utils/generateRange';
-import type {Connection} from '../../lib/OnyxConnectionManager';
-import type {CacheTask} from '../../lib/OnyxCache';
+import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
const MOCK_TASK = 'mockTask' as CacheTask;
describe('Onyx', () => {
- beforeAll(() => {
- // Disables concurrent rendering as it breaks withOnyx() tests.
- configureRNTL({
- concurrentRoot: false,
- });
- });
-
- afterAll(() => {
- resetRNTLToDefaults();
- });
-
describe('Cache Service', () => {
/** @type OnyxCache */
let cache: typeof OnyxCache;
@@ -437,7 +419,6 @@ describe('Onyx', () => {
describe('Onyx with Cache', () => {
let Onyx: typeof OnyxInstance;
let StorageMock: typeof MockedStorage;
- let withOnyx: typeof withOnyxType;
/** @type OnyxCache */
let cache: typeof OnyxCache;
@@ -470,72 +451,12 @@ describe('Onyx', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const OnyxModule = require('../../lib');
Onyx = OnyxModule.default;
- withOnyx = OnyxModule.withOnyx;
// eslint-disable-next-line @typescript-eslint/no-var-requires
StorageMock = require('../../lib/storage').default;
// eslint-disable-next-line @typescript-eslint/no-var-requires
cache = require('../../lib/OnyxCache').default;
});
- it('Expect a single call to getItem when multiple components use the same key', () => {
- // Given a component connected to Onyx
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.TEST_KEY,
- },
- })(ViewWithText);
-
- // Given some string value for that key exists in storage
- StorageMock.getItem.mockResolvedValue('"mockValue"');
- StorageMock.getAllKeys.mockResolvedValue([ONYX_KEYS.TEST_KEY]);
- return initOnyx()
- .then(() => {
- // When multiple components are rendered
- render(
- <>
-
-
-
- >,
- );
- })
- .then(waitForPromisesToResolve)
- .then(() => {
- // Then Async storage `getItem` should be called only once
- expect(StorageMock.getItem).toHaveBeenCalledTimes(1);
- });
- });
-
- it('Expect a single call to getAllKeys when multiple components use the same key', () => {
- // Given a component connected to Onyx
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.TEST_KEY,
- },
- })(ViewWithText);
-
- // Given some string value for that key exists in storage
- return initOnyx()
- .then(() => {
- StorageMock.getItem.mockResolvedValue('"mockValue"');
- StorageMock.getAllKeys.mockResolvedValue([ONYX_KEYS.TEST_KEY]);
-
- // When multiple components are rendered
- render(
- <>
-
-
-
- >,
- );
- })
- .then(waitForPromisesToResolve)
- .then(() => {
- // Then Async storage `getItem` should be called only once
- expect(StorageMock.getAllKeys).toHaveBeenCalledTimes(1);
- });
- });
-
it('Should keep recently accessed items in cache', () => {
// Given Storage with 10 different keys
StorageMock.getItem.mockResolvedValue('"mockValue"');
@@ -573,77 +494,6 @@ describe('Onyx', () => {
});
});
- it('Expect multiple calls to getItem when value cannot be retrieved from cache', () => {
- // Given a component connected to Onyx
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.TEST_KEY,
- },
- })(ViewWithText);
-
- // Given some string value for that key exists in storage
- StorageMock.getItem.mockResolvedValue('"mockValue"');
- StorageMock.getAllKeys.mockResolvedValue([ONYX_KEYS.TEST_KEY]);
-
- return (
- initOnyx()
- .then(() => {
- // When a component is rendered
- render();
- })
- .then(waitForPromisesToResolve)
- .then(() => {
- // When the key was removed from cache
- cache.drop(ONYX_KEYS.TEST_KEY);
- })
-
- // Then When another component using the same storage key is rendered
- .then(() => render())
- .then(waitForPromisesToResolve)
- .then(() => {
- // Then Async storage `getItem` should be called twice
- expect(StorageMock.getItem).toHaveBeenCalledTimes(2);
- })
- );
- });
-
- it('Expect multiple calls to getItem when multiple keys are used', () => {
- // Given two component
- const TestComponentWithOnyx = withOnyx({
- testObject: {
- key: ONYX_KEYS.TEST_KEY,
- },
- })(ViewWithCollections);
-
- const OtherTestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.OTHER_TEST,
- },
- })(ViewWithText);
-
- // Given some values exist in storage
- StorageMock.setItem(ONYX_KEYS.TEST_KEY, {ID: 15, data: 'mock object with ID'});
- StorageMock.setItem(ONYX_KEYS.OTHER_TEST, 'mock text');
- StorageMock.getAllKeys.mockResolvedValue([ONYX_KEYS.TEST_KEY, ONYX_KEYS.OTHER_TEST]);
- return initOnyx()
- .then(() => {
- // When the components are rendered multiple times
- render();
- render();
- render();
- render();
- render();
- render();
- })
- .then(waitForPromisesToResolve)
- .then(() => {
- // Then Async storage `getItem` should be called exactly two times (once for each key)
- expect(StorageMock.getItem).toHaveBeenCalledTimes(2);
- expect(StorageMock.getItem).toHaveBeenNthCalledWith(1, ONYX_KEYS.TEST_KEY);
- expect(StorageMock.getItem).toHaveBeenNthCalledWith(2, ONYX_KEYS.OTHER_TEST);
- });
- });
-
it('Should clean cache when connections to eviction keys happen', () => {
// Given storage with some data
StorageMock.getItem.mockResolvedValue('"mockValue"');
diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts
index 7d2679392..9b120a580 100644
--- a/tests/unit/onyxUtilsTest.ts
+++ b/tests/unit/onyxUtilsTest.ts
@@ -296,7 +296,6 @@ describe('OnyxUtils', () => {
OnyxUtils.keyChanged(
entryKey,
newEntryData, // Second update with different data
- updatedEntryData, // previous entry data
() => true, // notify connect subscribers
);
diff --git a/tests/unit/subscribeToPropertiesTest.tsx b/tests/unit/subscribeToPropertiesTest.tsx
deleted file mode 100644
index 7066219df..000000000
--- a/tests/unit/subscribeToPropertiesTest.tsx
+++ /dev/null
@@ -1,279 +0,0 @@
-import type {ErrorInfo, ReactNode} from 'react';
-import React from 'react';
-import {render, cleanup, configure as configureRNTL, resetToDefaults as resetRNTLToDefaults} from '@testing-library/react-native';
-import Onyx, {withOnyx} from '../../lib';
-import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
-import type {ViewWithObjectProps} from '../components/ViewWithObject';
-import ViewWithObject from '../components/ViewWithObject';
-import type GenericCollection from '../utils/GenericCollection';
-
-const ONYX_KEYS = {
- TEST_KEY: 'test',
- COLLECTION: {
- TEST_KEY: 'test_',
- },
-};
-
-Onyx.init({
- keys: ONYX_KEYS,
-});
-
-interface ErrorBoundaryProps {
- children?: ReactNode;
-}
-
-// The error boundary is here so that it will catch errors thrown in the selector methods (like syntax errors).
-// Normally, those errors get swallowed up by Jest and are not displayed so there was no indication that a test failed
-class ErrorBoundary extends React.Component {
- // Error boundaries have to implement this method. It's for providing a fallback UI, but
- // we don't need that for unit testing, so this is basically a no-op.
- static getDerivedStateFromError(error: Error) {
- return {error};
- }
-
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- console.error(error, errorInfo);
- }
-
- render() {
- // eslint-disable-next-line react/prop-types
- return this.props.children;
- }
-}
-
-describe('Only the specific property changes when using withOnyx() and ', () => {
- beforeAll(() => {
- // Disables concurrent rendering as it breaks withOnyx() tests.
- configureRNTL({
- concurrentRoot: false,
- });
- });
-
- afterAll(() => {
- resetRNTLToDefaults();
- });
-
- // Cleanup (ie. unmount) all rendered components and clear out Onyx after each test so that each test starts
- // with a clean slate
- afterEach(() => {
- cleanup();
- Onyx.clear();
- });
-
- /**
- * Runs all the assertions needed for withOnyx and using single keys in Onyx
- */
- const runAssertionsWithComponent = (TestComponentWithOnyx: React.ComponentType) => {
- let renderedComponent = render(
-
-
- ,
- );
- return (
- waitForPromisesToResolve()
- // When Onyx is updated with an object that has multiple properties
- .then(() => Onyx.merge(ONYX_KEYS.TEST_KEY, {a: 'one', b: 'two'}))
- .then(() => {
- renderedComponent = render(
-
-
- ,
- );
- })
-
- // Then the props passed to the component should only include the property "a" that was specified
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"propertyA":"one"}');
- })
-
- // When Onyx is updated with a change to property a
- .then(() => Onyx.merge(ONYX_KEYS.TEST_KEY, {a: 'two'}))
- .then(() => {
- renderedComponent = render(
-
-
- ,
- );
- })
-
- // Then the props passed should have the new value of property "a"
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"propertyA":"two"}');
- })
-
- // When Onyx is updated with a change to property b
- .then(() => Onyx.merge(ONYX_KEYS.TEST_KEY, {b: 'two'}))
- .then(() => {
- renderedComponent = render(
-
-
- ,
- );
- })
-
- // Then the props passed should not have changed
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"propertyA":"two"}');
- })
- );
- };
-
- it('connecting to a single non-collection key with a selector function', () => {
- const mockedSelector = jest.fn((obj) => obj && obj.a);
- const TestComponentWithOnyx = withOnyx({
- propertyA: {
- key: ONYX_KEYS.TEST_KEY,
- selector: mockedSelector,
- },
- })(ViewWithObject);
- return runAssertionsWithComponent(TestComponentWithOnyx).then(() => {
- // This checks to make sure a bug doesn't occur where the entire state object was being passed to
- // the selector
- expect(mockedSelector).not.toHaveBeenCalledWith({loading: false, propertyA: null});
- });
- });
-
- /**
- * Runs all the assertions for connecting to a full collection
- */
- const runAllAssertionsForCollection = (TestComponentWithOnyx: React.ComponentType) => {
- let renderedComponent = render(
-
-
- ,
- );
- return (
- waitForPromisesToResolve()
- // When Onyx is updated with an object that has multiple properties
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {a: 'one', b: 'two'},
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: {c: 'three', d: 'four'},
- } as GenericCollection),
- )
- .then(() => {
- renderedComponent = render(
-
-
- ,
- );
- })
-
- // Then the props passed to the component should only include the property "a" that was specified
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"collectionWithPropertyA":{"test_1":"one"}}');
- })
-
- // When Onyx is updated with a change to property a using merge()
- // This uses merge() just to make sure that everything works as expected when mixing merge()
- // and mergeCollection()
- .then(() => Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, {a: 'two'}))
-
- // Then the props passed should have the new value of property "a"
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"collectionWithPropertyA":{"test_1":"two"}}');
- })
-
- // When Onyx is updated with a change to property b using mergeCollection()
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {b: 'three'},
- } as GenericCollection),
- )
-
- // Then the props passed should not have changed
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"collectionWithPropertyA":{"test_1":"two"}}');
- })
- );
- };
-
- it('connecting to a collection with a selector function', () => {
- const mockedSelector = jest.fn((obj) => obj && obj.a);
- const TestComponentWithOnyx = withOnyx({
- collectionWithPropertyA: {
- key: ONYX_KEYS.COLLECTION.TEST_KEY,
- selector: mockedSelector,
- },
- })(ViewWithObject);
- return runAllAssertionsForCollection(TestComponentWithOnyx).then(() => {
- // Expect that the selector always gets called with the full object
- // from the onyx state, and not with the selector result value (string in this case).
-
- for (const mockedCall of mockedSelector.mock.calls) {
- const firstArg = mockedCall[0];
- expect(firstArg).toBeDefined();
- expect(firstArg).toBeInstanceOf(Object);
- }
-
- // Check to make sure that the selector was called with the props that are passed to the rendered component
- expect(mockedSelector).toHaveBeenNthCalledWith(5, {a: 'two', b: 'two'}, {loading: false, collectionWithPropertyA: {test_1: 'one', test_2: undefined}});
- });
- });
-
- /**
- * Runs all the assertions when connecting to a key that is part of a collection
- */
- const runAllAssertionsForCollectionMemberKey = (TestComponentWithOnyx: React.ComponentType) => {
- let renderedComponent = render(
-
-
- ,
- );
- return (
- waitForPromisesToResolve()
- // When Onyx is updated with an object that has multiple properties
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {a: 'one', b: 'two'},
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: {c: 'three', d: 'four'},
- } as GenericCollection),
- )
- .then(() => {
- renderedComponent = render(
-
-
- ,
- );
- })
-
- // Then the props passed to the component should only include the property "a" that was specified
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"itemWithPropertyA":"one"}');
- })
-
- // When Onyx is updated with a change to property a using merge()
- // This uses merge() just to make sure that everything works as expected when mixing merge()
- // and mergeCollection()
- .then(() => Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, {a: 'two'}))
-
- // Then the props passed should have the new value of property "a"
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"itemWithPropertyA":"two"}');
- })
-
- // When Onyx is updated with a change to property b using mergeCollection()
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {b: 'three'},
- } as GenericCollection),
- )
-
- // Then the props passed should not have changed
- .then(() => {
- expect(renderedComponent.getByTestId('text-element').props.children).toEqual('{"itemWithPropertyA":"two"}');
- })
- );
- };
-
- it('connecting to a collection member with a selector function', () => {
- const TestComponentWithOnyx = withOnyx({
- itemWithPropertyA: {
- key: `${ONYX_KEYS.COLLECTION.TEST_KEY}1`,
- // @ts-expect-error bypass
- selector: (obj) => obj && obj.a,
- },
- })(ViewWithObject);
- return runAllAssertionsForCollectionMemberKey(TestComponentWithOnyx);
- });
-});
diff --git a/tests/unit/withOnyxTest.tsx b/tests/unit/withOnyxTest.tsx
deleted file mode 100644
index d7f68bc1f..000000000
--- a/tests/unit/withOnyxTest.tsx
+++ /dev/null
@@ -1,805 +0,0 @@
-import React from 'react';
-import {act, render, configure as configureRNTL, resetToDefaults as resetRNTLToDefaults} from '@testing-library/react-native';
-import Onyx, {withOnyx} from '../../lib';
-import type {ViewWithTextOnyxProps, ViewWithTextProps} from '../components/ViewWithText';
-import ViewWithText from '../components/ViewWithText';
-import type {ViewWithCollectionsProps} from '../components/ViewWithCollections';
-import ViewWithCollections from '../components/ViewWithCollections';
-import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
-import type {ViewWithObjectProps} from '../components/ViewWithObject';
-import ViewWithObject from '../components/ViewWithObject';
-import StorageMock from '../../lib/storage';
-import type {OnyxValue} from '../../lib/types';
-import type GenericCollection from '../utils/GenericCollection';
-
-const ONYX_KEYS = {
- TEST_KEY: 'test',
- COLLECTION: {
- TEST_KEY: 'test_',
- STATIC: 'static_',
- DEPENDS_ON_STATIC: 'dependsOnStatic_',
- DEPENDS_ON_DEPENDS_ON_STATIC: 'dependsOnDependsOnStatic_',
- DEPENDS_ON_DEPENDS_ON_DEPENDS_ON_STATIC: 'dependsOnDependsOnDependsOnStatic_',
- },
- SIMPLE_KEY: 'simple',
- SIMPLE_KEY_2: 'simple2',
-};
-
-Onyx.init({
- keys: ONYX_KEYS,
- skippableCollectionMemberIDs: ['skippable-id'],
-});
-
-beforeEach(() => Onyx.clear());
-
-describe('withOnyxTest', () => {
- beforeAll(() => {
- // Disables concurrent rendering as it breaks withOnyx() tests.
- configureRNTL({
- concurrentRoot: false,
- });
- });
-
- afterAll(() => {
- resetRNTLToDefaults();
- });
-
- it('should render immediately with the test data when using withOnyx', () => {
- const onRender = jest.fn();
-
- // Note: earlier, after rendering a component we had to do another
- // waitForPromisesToResolve() to wait for Onyx's next tick updating
- // the component from {loading: true} to {loading:false, ...data}.
- // We now changed the architecture, so that when a key can be retrieved
- // synchronously from cache, we expect the component to be rendered immediately.
- return Onyx.set(ONYX_KEYS.TEST_KEY, 'test1')
- .then(() => {
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.TEST_KEY,
- },
- })(ViewWithText);
-
- const result = render();
- const textComponent = result.getByText('test1');
- expect(textComponent).not.toBeNull();
-
- return waitForPromisesToResolve();
- })
- .then(() => {
- // As the component immediately rendered from cache, we want to make
- // sure it wasn't updated a second time with the same value (the connect
- // calls gets the data another time and tries to forward it to the component,
- // which could cause a re-render if the right checks aren't in place):
- expect(onRender).toHaveBeenCalledTimes(1);
- });
- });
-
- it('should update withOnyx subscriber multiple times when merge is used', () => {
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.COLLECTION.TEST_KEY,
- },
- })(ViewWithCollections);
- const onRender = jest.fn();
- render();
-
- return waitForPromisesToResolve()
- .then(() => {
- Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, {ID: 123});
- Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}2`, {ID: 234});
- return Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}3`, {ID: 345});
- })
- .then(() => {
- // We expect 2 due to batching
- expect(onRender).toHaveBeenCalledTimes(2);
- });
- });
-
- it('should batch correctly together little khachapuris', () =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {ID: 999},
- } as GenericCollection)
- .then(() => Onyx.merge(ONYX_KEYS.SIMPLE_KEY, 'prev_string'))
- .then(() => Onyx.merge(ONYX_KEYS.SIMPLE_KEY_2, 'prev_string2'))
- .then(() => {
- const TestComponentWithOnyx = withOnyx>({
- testKey: {
- key: `${ONYX_KEYS.COLLECTION.TEST_KEY}1`,
- },
- simpleKey: {
- key: ONYX_KEYS.SIMPLE_KEY,
- },
- simpleKey2: {
- key: ONYX_KEYS.SIMPLE_KEY_2,
- },
- })(ViewWithObject);
- const onRender = jest.fn();
- render();
-
- return waitForPromisesToResolve()
- .then(() => {
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_1: {ID: 123}} as GenericCollection);
- Onyx.merge(ONYX_KEYS.SIMPLE_KEY, 'string');
- return Onyx.merge(ONYX_KEYS.SIMPLE_KEY_2, 'string2');
- })
- .then(() => {
- // We expect it to be 2 as we first is initial render and second are 3 Onyx merges batched together.
- // As you see onyx merges on the top of the function doesn't account they are done earlier
- expect(onRender).toHaveBeenCalledTimes(2);
- });
- }));
-
- it('should update withOnyx subscriber just once when mergeCollection is used', () => {
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: ONYX_KEYS.COLLECTION.TEST_KEY,
- },
- })(ViewWithCollections);
- const onRender = jest.fn();
- render();
- return waitForPromisesToResolve()
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- test_1: {ID: 123},
- test_2: {ID: 234},
- test_3: {ID: 345},
- } as GenericCollection),
- )
- .then(() => {
- expect(onRender).toHaveBeenCalledTimes(2);
- });
- });
-
- it('should update withOnyx subscribing to individual key if mergeCollection is used', () => {
- const collectionItemID = 1;
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: `${ONYX_KEYS.COLLECTION.TEST_KEY}${collectionItemID}`,
- },
- })(ViewWithCollections);
- const onRender = jest.fn();
- render();
- return waitForPromisesToResolve()
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- test_1: {ID: 123},
- test_2: {ID: 234},
- test_3: {ID: 345},
- } as GenericCollection),
- )
- .then(() => {
- expect(onRender).toHaveBeenCalledTimes(2);
- });
- });
-
- it('should replace arrays inside objects with withOnyx subscribing to individual key if mergeCollection is used', () => {
- const collectionItemID = 1;
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: `${ONYX_KEYS.COLLECTION.TEST_KEY}${collectionItemID}`,
- },
- })(ViewWithCollections);
- const onRender = jest.fn();
- const markReadyForHydration = jest.fn();
- render(
- ,
- );
- return waitForPromisesToResolve()
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- test_1: {list: [1, 2]},
- } as GenericCollection),
- )
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- test_1: {list: [7]},
- } as GenericCollection),
- )
- .then(() => {
- expect(onRender).toHaveBeenCalledTimes(3);
- expect(onRender).toHaveBeenLastCalledWith({
- collections: {},
- markReadyForHydration,
- onRender,
- testObject: {isDefaultProp: true},
- text: {list: [7]},
- });
- });
- });
-
- it('should update withOnyx subscribing to individual key with merged value if mergeCollection is used', () => {
- const collectionItemID = 4;
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: `${ONYX_KEYS.COLLECTION.TEST_KEY}${collectionItemID}`,
- },
- })(ViewWithCollections);
- const onRender = jest.fn();
- const markReadyForHydration = jest.fn();
- render(
- ,
- );
- return waitForPromisesToResolve()
- .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_4: {ID: 456}, test_5: {ID: 567}} as GenericCollection))
- .then(() =>
- Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
- test_4: {Name: 'Test4'},
- test_5: {Name: 'Test5'},
- test_6: {ID: 678, Name: 'Test6'},
- } as GenericCollection),
- )
- .then(() => {
- expect(onRender).toHaveBeenCalledTimes(3);
- expect(onRender).toHaveBeenLastCalledWith({
- collections: {},
- markReadyForHydration,
- onRender,
- testObject: {isDefaultProp: true},
- text: {ID: 456, Name: 'Test4'},
- });
- });
- });
-
- it('should update if a prop dependent key changes', () => {
- let rerender: ReturnType['rerender'];
- let getByTestId: ReturnType['getByTestId'];
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: (props) => `${ONYX_KEYS.COLLECTION.TEST_KEY}${props.collectionID}`,
- },
- })(ViewWithText);
- Onyx.set(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, 'test_1');
- Onyx.set(`${ONYX_KEYS.COLLECTION.TEST_KEY}2`, 'test_2');
- return waitForPromisesToResolve()
- .then(() => {
- const result = render();
- rerender = result.rerender;
- getByTestId = result.getByTestId;
- })
- .then(() => {
- expect(getByTestId('text-element').props.children).toEqual('test_1');
- })
- .then(() => {
- rerender();
-
- // Note, when we change the prop, we need to wait for the next tick:
- return waitForPromisesToResolve();
- })
- .then(waitForPromisesToResolve)
- .then(() => {
- expect(getByTestId('text-element').props.children).toEqual('test_2');
- });
- });
-
- it('should render the WrappedComponent if no keys are required for init', () => {
- const INITIAL_VALUE = 'initial_value';
- const TestComponentWithOnyx = withOnyx({
- text: {
- key: 'test',
- initWithStoredValues: false,
- },
- })(ViewWithText);
- TestComponentWithOnyx.defaultProps = {
- text: INITIAL_VALUE,
- } as ViewWithTextProps;
- Onyx.set('test', 'test_text');
- return waitForPromisesToResolve().then(() => {
- const {getByTestId} = render(