react-native-onyx 3.0.115 → 3.0.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +15 -5
- package/README.md +8 -8
- package/dist/Onyx.d.ts +17 -14
- package/dist/Onyx.js +132 -41
- package/dist/OnyxCache.d.ts +2 -2
- package/dist/OnyxCache.js +4 -9
- package/dist/OnyxUtils.d.ts +24 -55
- package/dist/OnyxUtils.js +125 -352
- package/dist/PendingWrites.d.ts +35 -0
- package/dist/PendingWrites.js +103 -0
- package/dist/index.d.ts +1 -2
- package/dist/types.d.ts +10 -13
- package/dist/useOnyx.d.ts +11 -10
- package/dist/useOnyx.js +68 -193
- package/package.json +4 -2
- package/dist/OnyxConnectionManager.d.ts +0 -78
- package/dist/OnyxConnectionManager.js +0 -189
- package/dist/OnyxSnapshotCache.d.ts +0 -75
- package/dist/OnyxSnapshotCache.js +0 -125
- package/dist/createMemoizedSelector.d.ts +0 -18
- package/dist/createMemoizedSelector.js +0 -37
- package/dist/memoizedShallowEqual.d.ts +0 -5
- package/dist/memoizedShallowEqual.js +0 -34
package/API.md
CHANGED
|
@@ -78,36 +78,40 @@ Connects to an Onyx key given the options passed and listens to its changes.
|
|
|
78
78
|
This method will be deprecated soon. Please use `Onyx.connectWithoutView()` instead.
|
|
79
79
|
|
|
80
80
|
**Kind**: global function
|
|
81
|
-
**Returns**: The
|
|
81
|
+
**Returns**: The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
82
82
|
|
|
83
83
|
| Param | Description |
|
|
84
84
|
| --- | --- |
|
|
85
85
|
| connectOptions | The options object that will define the behavior of the connection. |
|
|
86
86
|
| connectOptions.key | The Onyx key to subscribe to. |
|
|
87
87
|
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
|
|
88
|
-
| 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). |
|
|
89
88
|
|
|
90
89
|
**Example**
|
|
91
90
|
```ts
|
|
92
|
-
const connection = Onyx.
|
|
91
|
+
const connection = Onyx.connect({
|
|
93
92
|
key: ONYXKEYS.SESSION,
|
|
94
93
|
callback: onSessionChange,
|
|
95
94
|
});
|
|
96
95
|
```
|
|
96
|
+
|
|
97
|
+
For a collection root key, the callback fires with the entire frozen collection
|
|
98
|
+
object whenever any member changes; signature `(collection, collectionKey)`.
|
|
99
|
+
For any other key, the callback fires with the value at that key; signature
|
|
100
|
+
`(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
101
|
+
cache after any same-tick writes have applied.
|
|
97
102
|
<a name="connectWithoutView"></a>
|
|
98
103
|
|
|
99
104
|
## connectWithoutView(connectOptions) ⇒
|
|
100
105
|
Connects to an Onyx key given the options passed and listens to its changes.
|
|
101
106
|
|
|
102
107
|
**Kind**: global function
|
|
103
|
-
**Returns**: The
|
|
108
|
+
**Returns**: The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
104
109
|
|
|
105
110
|
| Param | Description |
|
|
106
111
|
| --- | --- |
|
|
107
112
|
| connectOptions | The options object that will define the behavior of the connection. |
|
|
108
113
|
| connectOptions.key | The Onyx key to subscribe to. |
|
|
109
114
|
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
|
|
110
|
-
| 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). |
|
|
111
115
|
|
|
112
116
|
**Example**
|
|
113
117
|
```ts
|
|
@@ -116,6 +120,12 @@ const connection = Onyx.connectWithoutView({
|
|
|
116
120
|
callback: onSessionChange,
|
|
117
121
|
});
|
|
118
122
|
```
|
|
123
|
+
|
|
124
|
+
For a collection root key, the callback fires with the entire frozen collection
|
|
125
|
+
object whenever any member changes; signature `(collection, collectionKey)`.
|
|
126
|
+
For any other key, the callback fires with the value at that key; signature
|
|
127
|
+
`(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
128
|
+
cache after any same-tick writes have applied.
|
|
119
129
|
<a name="disconnect"></a>
|
|
120
130
|
|
|
121
131
|
## disconnect(connection)
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Awesome persistent storage solution wrapped in a Pub/Sub library.
|
|
|
9
9
|
- Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed
|
|
10
10
|
- Anything needing to read Onyx data needs to:
|
|
11
11
|
1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > local storage)
|
|
12
|
-
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.
|
|
12
|
+
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.connectWithoutView()`.
|
|
13
13
|
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)
|
|
14
14
|
- 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`.
|
|
15
15
|
|
|
@@ -120,20 +120,20 @@ You should avoid arrays as much as possible. They do not work well with `merge()
|
|
|
120
120
|
|
|
121
121
|
## Subscribing to data changes
|
|
122
122
|
|
|
123
|
-
To set up a basic subscription for a given key use the `Onyx.
|
|
123
|
+
To set up a basic subscription for a given key outside of a React component use the `Onyx.connectWithoutView()` method. It returns a connection handle.
|
|
124
124
|
|
|
125
125
|
```javascript
|
|
126
126
|
let session;
|
|
127
|
-
const
|
|
127
|
+
const connection = Onyx.connectWithoutView({
|
|
128
128
|
key: ONYXKEYS.SESSION,
|
|
129
129
|
callback: (val) => session = val || {},
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
To teardown the subscription call `Onyx.disconnect()` with the
|
|
133
|
+
To teardown the subscription call `Onyx.disconnect()` with the connection returned from `Onyx.connectWithoutView()`. It's recommended to clean up subscriptions anytime you are connecting from within a function to prevent memory leaks.
|
|
134
134
|
|
|
135
135
|
```javascript
|
|
136
|
-
Onyx.disconnect(
|
|
136
|
+
Onyx.disconnect(connection);
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
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.
|
|
@@ -204,7 +204,7 @@ export default App;
|
|
|
204
204
|
|
|
205
205
|
* 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.
|
|
206
206
|
|
|
207
|
-
It's also beneficial to use
|
|
207
|
+
It's also beneficial to use `useOnyx()`'s `selector` option in case you need to grab a single item in a collection (like a single report action).
|
|
208
208
|
|
|
209
209
|
## Collections
|
|
210
210
|
|
|
@@ -257,7 +257,7 @@ export default MyComponent;
|
|
|
257
257
|
This will add a prop to the component called `allReports` which is an object of collection member key/values. Changes to the individual member keys will modify the entire object and new props will be passed with each individual key update. The prop doesn't update on the initial rendering of the component until the entire collection has been read out of Onyx.
|
|
258
258
|
|
|
259
259
|
```js
|
|
260
|
-
Onyx.
|
|
260
|
+
Onyx.connectWithoutView({key: ONYXKEYS.COLLECTION.REPORT, callback: (allReports, collectionKey) => {...}});
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
This will fire the callback once with the entire collection initially and later with an updated version of the collection when individual keys update.
|
|
@@ -293,7 +293,7 @@ function signOut() {
|
|
|
293
293
|
`Onyx.exportState()` returns a plain object containing persisted keys and values from the active storage provider. It works on native and web without opening another database connection. The export excludes stale persisted rows for keys that are now RAM-only. Current RAM-only values and writes still in progress are never included. Treat the returned object and its nested values as read-only because some providers return references to their stored values. The result can contain sensitive data; callers should redact it before sharing.
|
|
294
294
|
|
|
295
295
|
## Storage Providers
|
|
296
|
-
`Onyx.
|
|
296
|
+
`Onyx.set`, `Onyx.merge`, and the rest of the API accesses the underlying storage
|
|
297
297
|
differently depending on the platform
|
|
298
298
|
|
|
299
299
|
Under the hood storage access calls are delegated to a [`StorageProvider`](lib/storage/index.js)
|
package/dist/Onyx.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as Logger from './Logger';
|
|
2
|
-
import type { CollectionKeyBase, ConnectOptions, InitOptions, OnyxKey, OnyxMergeCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetCollectionInput, OnyxSetInput, OnyxUpdate, OnyxValue, SetOptions } from './types';
|
|
3
|
-
import type { Connection } from './OnyxConnectionManager';
|
|
2
|
+
import type { CollectionKeyBase, Connection, ConnectOptions, InitOptions, OnyxKey, OnyxMergeCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetCollectionInput, OnyxSetInput, OnyxUpdate, OnyxValue, SetOptions } from './types';
|
|
4
3
|
/** Initialize the store with actions and listening for storage events */
|
|
5
4
|
declare function init({ keys, initialKeyStates, evictableKeys, shouldSyncMultipleInstances, enableDevTools, skippableCollectionMemberIDs, ramOnlyKeys, snapshotMergeKeys, }: InitOptions): void;
|
|
6
5
|
/**
|
|
@@ -9,20 +8,22 @@ declare function init({ keys, initialKeyStates, evictableKeys, shouldSyncMultipl
|
|
|
9
8
|
*
|
|
10
9
|
* @example
|
|
11
10
|
* ```ts
|
|
12
|
-
* const connection = Onyx.
|
|
11
|
+
* const connection = Onyx.connect({
|
|
13
12
|
* key: ONYXKEYS.SESSION,
|
|
14
13
|
* callback: onSessionChange,
|
|
15
14
|
* });
|
|
16
15
|
* ```
|
|
17
16
|
*
|
|
17
|
+
* For a collection root key, the callback fires with the entire frozen collection
|
|
18
|
+
* object whenever any member changes; signature `(collection, collectionKey)`.
|
|
19
|
+
* For any other key, the callback fires with the value at that key; signature
|
|
20
|
+
* `(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
21
|
+
* cache after any same-tick writes have applied.
|
|
22
|
+
*
|
|
18
23
|
* @param connectOptions The options object that will define the behavior of the connection.
|
|
19
24
|
* @param connectOptions.key The Onyx key to subscribe to.
|
|
20
25
|
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
|
|
21
|
-
* @
|
|
22
|
-
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
|
|
23
|
-
* when the subset of data changes. Otherwise, any change of data on any property would normally
|
|
24
|
-
* cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
25
|
-
* @returns The connection object to use when calling `Onyx.disconnect()`.
|
|
26
|
+
* @returns The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
26
27
|
*/
|
|
27
28
|
declare function connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): Connection;
|
|
28
29
|
/**
|
|
@@ -36,14 +37,16 @@ declare function connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TK
|
|
|
36
37
|
* });
|
|
37
38
|
* ```
|
|
38
39
|
*
|
|
40
|
+
* For a collection root key, the callback fires with the entire frozen collection
|
|
41
|
+
* object whenever any member changes; signature `(collection, collectionKey)`.
|
|
42
|
+
* For any other key, the callback fires with the value at that key; signature
|
|
43
|
+
* `(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
44
|
+
* cache after any same-tick writes have applied.
|
|
45
|
+
*
|
|
39
46
|
* @param connectOptions The options object that will define the behavior of the connection.
|
|
40
47
|
* @param connectOptions.key The Onyx key to subscribe to.
|
|
41
48
|
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
|
|
42
|
-
* @
|
|
43
|
-
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
|
|
44
|
-
* when the subset of data changes. Otherwise, any change of data on any property would normally
|
|
45
|
-
* cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
46
|
-
* @returns The connection object to use when calling `Onyx.disconnect()`.
|
|
49
|
+
* @returns The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
47
50
|
*/
|
|
48
51
|
declare function connectWithoutView<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): Connection;
|
|
49
52
|
/**
|
|
@@ -182,4 +185,4 @@ declare const Onyx: {
|
|
|
182
185
|
registerLogger: typeof Logger.registerLogger;
|
|
183
186
|
};
|
|
184
187
|
export default Onyx;
|
|
185
|
-
export type { OnyxUpdate, ConnectOptions, SetOptions };
|
|
188
|
+
export type { OnyxUpdate, ConnectOptions, SetOptions, Connection };
|
package/dist/Onyx.js
CHANGED
|
@@ -43,8 +43,9 @@ const utils_1 = __importDefault(require("./utils"));
|
|
|
43
43
|
const DevTools_1 = __importStar(require("./DevTools"));
|
|
44
44
|
const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
|
|
45
45
|
const OnyxKeys_1 = __importDefault(require("./OnyxKeys"));
|
|
46
|
+
const PendingWrites_1 = __importDefault(require("./PendingWrites"));
|
|
46
47
|
const logMessages_1 = __importDefault(require("./logMessages"));
|
|
47
|
-
const
|
|
48
|
+
const OnyxSubscriptionManager_1 = __importDefault(require("./OnyxSubscriptionManager"));
|
|
48
49
|
const OnyxMerge_1 = __importDefault(require("./OnyxMerge"));
|
|
49
50
|
/** Initialize the store with actions and listening for storage events */
|
|
50
51
|
function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSyncMultipleInstances = !!global.localStorage, enableDevTools = true, skippableCollectionMemberIDs = [], ramOnlyKeys = [], snapshotMergeKeys = [], }) {
|
|
@@ -70,7 +71,7 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSync
|
|
|
70
71
|
}
|
|
71
72
|
const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
|
|
72
73
|
const isCollectionMember = !!collectionKey && OnyxKeys_1.default.isCollectionMemberKey(collectionKey, key);
|
|
73
|
-
// Capture the previous cached value
|
|
74
|
+
// Capture the previous cached value before cache.set() so notifyCollection() can diff old vs new per member.
|
|
74
75
|
const previousValue = isCollectionMember ? OnyxCache_1.default.get(key) : undefined;
|
|
75
76
|
OnyxCache_1.default.set(key, value);
|
|
76
77
|
if (isCollectionMember && collectionKey) {
|
|
@@ -89,14 +90,14 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSync
|
|
|
89
90
|
individual.push([key, value]);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
|
-
// Non-collection keys: notify individually, matching
|
|
93
|
+
// Non-collection keys: notify individually, matching notifyKey() semantics for exact keys.
|
|
93
94
|
for (const [key, value] of individual) {
|
|
94
|
-
OnyxUtils_1.default.
|
|
95
|
+
OnyxUtils_1.default.notifyKey(key, value);
|
|
95
96
|
}
|
|
96
|
-
// One
|
|
97
|
-
//
|
|
97
|
+
// One notifyCollection() per collection notifies the collection-root subscriber once and lets
|
|
98
|
+
// notifyCollection() decide which individual member subscribers actually changed.
|
|
98
99
|
for (const [collectionKey, { partial, previous }] of collectionBatches) {
|
|
99
|
-
OnyxUtils_1.default.
|
|
100
|
+
OnyxUtils_1.default.notifyCollection(collectionKey, partial, previous);
|
|
100
101
|
}
|
|
101
102
|
});
|
|
102
103
|
}
|
|
@@ -115,23 +116,89 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSync
|
|
|
115
116
|
*
|
|
116
117
|
* @example
|
|
117
118
|
* ```ts
|
|
118
|
-
* const connection = Onyx.
|
|
119
|
+
* const connection = Onyx.connect({
|
|
119
120
|
* key: ONYXKEYS.SESSION,
|
|
120
121
|
* callback: onSessionChange,
|
|
121
122
|
* });
|
|
122
123
|
* ```
|
|
123
124
|
*
|
|
125
|
+
* For a collection root key, the callback fires with the entire frozen collection
|
|
126
|
+
* object whenever any member changes; signature `(collection, collectionKey)`.
|
|
127
|
+
* For any other key, the callback fires with the value at that key; signature
|
|
128
|
+
* `(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
129
|
+
* cache after any same-tick writes have applied.
|
|
130
|
+
*
|
|
124
131
|
* @param connectOptions The options object that will define the behavior of the connection.
|
|
125
132
|
* @param connectOptions.key The Onyx key to subscribe to.
|
|
126
133
|
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
|
|
127
|
-
* @
|
|
128
|
-
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
|
|
129
|
-
* when the subset of data changes. Otherwise, any change of data on any property would normally
|
|
130
|
-
* cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
131
|
-
* @returns The connection object to use when calling `Onyx.disconnect()`.
|
|
134
|
+
* @returns The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
132
135
|
*/
|
|
133
136
|
function connect(connectOptions) {
|
|
134
|
-
|
|
137
|
+
const { key, callback } = connectOptions;
|
|
138
|
+
let active = true;
|
|
139
|
+
let unsubscribeFn = null;
|
|
140
|
+
const startSubscription = () => {
|
|
141
|
+
if (!active) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (OnyxKeys_1.default.isCollectionKey(key)) {
|
|
145
|
+
// Collection-root mode: dedup skips identical collection refs. Initial fire delivers
|
|
146
|
+
// the current collection object: frozen `{}` for an empty-but-known collection,
|
|
147
|
+
// `undefined` only if the collection key has not been seen yet.
|
|
148
|
+
let lastDeliveredCollection = OnyxUtils_1.default.NOT_DELIVERED;
|
|
149
|
+
const deliverCollection = (rawCollection, k) => {
|
|
150
|
+
if (Object.is(lastDeliveredCollection, rawCollection)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
lastDeliveredCollection = rawCollection;
|
|
154
|
+
callback === null || callback === void 0 ? void 0 : callback(rawCollection, k);
|
|
155
|
+
};
|
|
156
|
+
unsubscribeFn = OnyxSubscriptionManager_1.default.subscribe(key, (value, k) => {
|
|
157
|
+
deliverCollection(value, k);
|
|
158
|
+
});
|
|
159
|
+
PendingWrites_1.default.scheduleInitialSubscriberNotification(key, () => {
|
|
160
|
+
if (!active) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
deliverCollection(OnyxSubscriptionManager_1.default.getState(key), key);
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Non-collection key (or a specific collection member): single-value subscription.
|
|
168
|
+
let lastDelivered = OnyxUtils_1.default.NOT_DELIVERED;
|
|
169
|
+
const deliverValue = (value, k) => {
|
|
170
|
+
if (Object.is(lastDelivered, value)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
lastDelivered = value;
|
|
174
|
+
callback === null || callback === void 0 ? void 0 : callback(value, k);
|
|
175
|
+
};
|
|
176
|
+
unsubscribeFn = OnyxSubscriptionManager_1.default.subscribe(key, (value, k) => {
|
|
177
|
+
deliverValue(value, k);
|
|
178
|
+
});
|
|
179
|
+
PendingWrites_1.default.scheduleInitialSubscriberNotification(key, () => {
|
|
180
|
+
if (!active) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
deliverValue(OnyxSubscriptionManager_1.default.getState(key), key);
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
OnyxUtils_1.default.afterInit(() => {
|
|
187
|
+
startSubscription();
|
|
188
|
+
return Promise.resolve();
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
unsubscribe: () => {
|
|
192
|
+
if (!active) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
active = false;
|
|
196
|
+
if (unsubscribeFn) {
|
|
197
|
+
unsubscribeFn();
|
|
198
|
+
unsubscribeFn = null;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
135
202
|
}
|
|
136
203
|
/**
|
|
137
204
|
* Connects to an Onyx key given the options passed and listens to its changes.
|
|
@@ -144,17 +211,19 @@ function connect(connectOptions) {
|
|
|
144
211
|
* });
|
|
145
212
|
* ```
|
|
146
213
|
*
|
|
214
|
+
* For a collection root key, the callback fires with the entire frozen collection
|
|
215
|
+
* object whenever any member changes; signature `(collection, collectionKey)`.
|
|
216
|
+
* For any other key, the callback fires with the value at that key; signature
|
|
217
|
+
* `(value, key)`. Initial fire is deferred via `scheduleInitialSubscriberNotification` so it reads
|
|
218
|
+
* cache after any same-tick writes have applied.
|
|
219
|
+
*
|
|
147
220
|
* @param connectOptions The options object that will define the behavior of the connection.
|
|
148
221
|
* @param connectOptions.key The Onyx key to subscribe to.
|
|
149
222
|
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
|
|
150
|
-
* @
|
|
151
|
-
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
|
|
152
|
-
* when the subset of data changes. Otherwise, any change of data on any property would normally
|
|
153
|
-
* cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
154
|
-
* @returns The connection object to use when calling `Onyx.disconnect()`.
|
|
223
|
+
* @returns The `Connection` handle to use when calling `Onyx.disconnect()`.
|
|
155
224
|
*/
|
|
156
225
|
function connectWithoutView(connectOptions) {
|
|
157
|
-
return
|
|
226
|
+
return connect(connectOptions);
|
|
158
227
|
}
|
|
159
228
|
/**
|
|
160
229
|
* Disconnects and removes the listener from the Onyx key.
|
|
@@ -172,7 +241,10 @@ function connectWithoutView(connectOptions) {
|
|
|
172
241
|
* @param connection Connection object returned by calling `Onyx.connect()` or `Onyx.connectWithoutView()`.
|
|
173
242
|
*/
|
|
174
243
|
function disconnect(connection) {
|
|
175
|
-
|
|
244
|
+
if (!connection) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
connection.unsubscribe();
|
|
176
248
|
}
|
|
177
249
|
/**
|
|
178
250
|
* Write a value to our store with the given key
|
|
@@ -182,7 +254,7 @@ function disconnect(connection) {
|
|
|
182
254
|
* @param options optional configuration object
|
|
183
255
|
*/
|
|
184
256
|
function set(key, value, options) {
|
|
185
|
-
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setWithRetry({ key, value, options }));
|
|
257
|
+
return PendingWrites_1.default.trackPendingWrite(key, OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setWithRetry({ key, value, options })));
|
|
186
258
|
}
|
|
187
259
|
/**
|
|
188
260
|
* Sets multiple keys and values
|
|
@@ -192,7 +264,7 @@ function set(key, value, options) {
|
|
|
192
264
|
* @param data object keyed by ONYXKEYS and the values to set
|
|
193
265
|
*/
|
|
194
266
|
function multiSet(data) {
|
|
195
|
-
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.multiSetWithRetry(data));
|
|
267
|
+
return PendingWrites_1.default.trackPendingWrite(Object.keys(data), OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.multiSetWithRetry(data)));
|
|
196
268
|
}
|
|
197
269
|
/**
|
|
198
270
|
* Merge a new value into an existing value at a key.
|
|
@@ -211,7 +283,7 @@ function multiSet(data) {
|
|
|
211
283
|
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
|
|
212
284
|
*/
|
|
213
285
|
function merge(key, changes) {
|
|
214
|
-
return OnyxUtils_1.default.afterInit(() => {
|
|
286
|
+
return PendingWrites_1.default.trackPendingWrite(key, OnyxUtils_1.default.afterInit(() => {
|
|
215
287
|
const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
|
|
216
288
|
if (skippableCollectionMemberIDs.size) {
|
|
217
289
|
try {
|
|
@@ -233,22 +305,30 @@ function merge(key, changes) {
|
|
|
233
305
|
if (changes === undefined) {
|
|
234
306
|
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
|
|
235
307
|
}
|
|
236
|
-
// Merge attempts are batched together. The delta should be applied after a single
|
|
237
|
-
// Using the initial value from storage in subsequent
|
|
308
|
+
// Merge attempts are batched together. The delta should be applied after a single read of the
|
|
309
|
+
// existing value to prevent a race condition. Using the initial value from storage in subsequent
|
|
310
|
+
// merge attempts will lead to an incorrect final merged value.
|
|
238
311
|
if (mergeQueue[key]) {
|
|
239
312
|
mergeQueue[key].push(changes);
|
|
240
313
|
return mergeQueuePromise[key];
|
|
241
314
|
}
|
|
242
|
-
|
|
243
|
-
|
|
315
|
+
const queuedChanges = [changes];
|
|
316
|
+
mergeQueue[key] = queuedChanges;
|
|
317
|
+
// Eager hydration means an uncached key has nothing stored, and a storage round trip would land
|
|
318
|
+
// this write after the same update's cached keys, where subscribers can see a half-applied batch.
|
|
319
|
+
// A running `Onyx.clear` resets keys, so read what it leaves.
|
|
320
|
+
const readMergeBase = () => (OnyxCache_1.default.hasCacheForKey(key) ? OnyxCache_1.default.get(key) : undefined);
|
|
321
|
+
const pendingClear = OnyxCache_1.default.getTaskPromise(OnyxCache_1.TASK.CLEAR);
|
|
322
|
+
mergeQueuePromise[key] = (pendingClear ? pendingClear.then(readMergeBase) : Promise.resolve(readMergeBase())).then((valueFromGet) => {
|
|
244
323
|
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
|
|
245
|
-
if (mergeQueue[key]
|
|
324
|
+
if (mergeQueue[key] !== queuedChanges) {
|
|
246
325
|
return Promise.resolve();
|
|
247
326
|
}
|
|
248
327
|
// Other writers (notably Onyx.update's mergeCollection path, which doesn't participate in mergeQueue)
|
|
249
|
-
// can land between
|
|
328
|
+
// can land between the read above and this callback running. Applying the delta on top of the value
|
|
250
329
|
// captured back then and broadcasting it would overwrite those writes wholesale, so re-read the cache.
|
|
251
|
-
const
|
|
330
|
+
const baseValue = OnyxUtils_1.default.hasStaleMergeRead(queuedChanges) ? undefined : valueFromGet;
|
|
331
|
+
const existingValue = OnyxCache_1.default.hasCacheForKey(key) ? OnyxCache_1.default.get(key) : baseValue;
|
|
252
332
|
try {
|
|
253
333
|
const validChanges = mergeQueue[key].filter((change) => {
|
|
254
334
|
const { isCompatible, existingValueType, newValueType, isEmptyArrayCoercion } = utils_1.default.checkCompatibilityWithExistingValue(change, existingValue);
|
|
@@ -286,7 +366,7 @@ function merge(key, changes) {
|
|
|
286
366
|
}
|
|
287
367
|
});
|
|
288
368
|
return mergeQueuePromise[key];
|
|
289
|
-
});
|
|
369
|
+
}));
|
|
290
370
|
}
|
|
291
371
|
/**
|
|
292
372
|
* Merges a collection based on their keys.
|
|
@@ -302,7 +382,7 @@ function merge(key, changes) {
|
|
|
302
382
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
303
383
|
*/
|
|
304
384
|
function mergeCollection(collectionKey, collection) {
|
|
305
|
-
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection }));
|
|
385
|
+
return PendingWrites_1.default.trackPendingWrite(Object.keys(collection), OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection })));
|
|
306
386
|
}
|
|
307
387
|
/**
|
|
308
388
|
* Clear out all the data in the store
|
|
@@ -326,7 +406,8 @@ function mergeCollection(collectionKey, collection) {
|
|
|
326
406
|
* @param keysToPreserve is a list of ONYXKEYS that should not be cleared with the rest of the data
|
|
327
407
|
*/
|
|
328
408
|
function clear(keysToPreserve = []) {
|
|
329
|
-
return OnyxUtils_1.default.afterInit(() => {
|
|
409
|
+
return PendingWrites_1.default.trackPendingGlobalWrite(OnyxUtils_1.default.afterInit(() => {
|
|
410
|
+
const pendingMergeEntries = OnyxUtils_1.default.getPendingMergeEntries(keysToPreserve);
|
|
330
411
|
const defaultKeyStates = OnyxUtils_1.default.getDefaultKeyStates();
|
|
331
412
|
const initialKeys = Object.keys(defaultKeyStates);
|
|
332
413
|
const promise = OnyxUtils_1.default.getAllKeys()
|
|
@@ -339,6 +420,7 @@ function clear(keysToPreserve = []) {
|
|
|
339
420
|
// because the notification process needs the old values in cache but at that point they will be already removed from it.
|
|
340
421
|
const keyValuesToResetAsCollection = {};
|
|
341
422
|
const allKeys = new Set([...cachedKeys, ...initialKeys]);
|
|
423
|
+
OnyxUtils_1.default.cancelPendingMerges(pendingMergeEntries);
|
|
342
424
|
// The only keys that should not be cleared are:
|
|
343
425
|
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
|
|
344
426
|
// status, or activeClients need to remain in Onyx even when signed out)
|
|
@@ -388,22 +470,21 @@ function clear(keysToPreserve = []) {
|
|
|
388
470
|
for (const key of keysToBeClearedFromStorage)
|
|
389
471
|
OnyxCache_1.default.drop(key);
|
|
390
472
|
return storage_1.default.removeItems(keysToBeClearedFromStorage)
|
|
391
|
-
.then(() => OnyxConnectionManager_1.default.refreshSessionID())
|
|
392
473
|
.then(() => storage_1.default.multiSet(defaultKeyValuePairs))
|
|
393
474
|
.then(() => {
|
|
394
475
|
DevTools_1.default.clearState(keysToPreserve);
|
|
395
476
|
// Notify the subscribers for each key/value group so they can receive the new values
|
|
396
477
|
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
|
|
397
|
-
OnyxUtils_1.default.
|
|
478
|
+
OnyxUtils_1.default.notifyKey(key, value);
|
|
398
479
|
}
|
|
399
480
|
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
|
|
400
|
-
OnyxUtils_1.default.
|
|
481
|
+
OnyxUtils_1.default.notifyCollection(key, value.newValues, value.oldValues);
|
|
401
482
|
}
|
|
402
483
|
});
|
|
403
484
|
})
|
|
404
485
|
.then(() => undefined);
|
|
405
486
|
return OnyxCache_1.default.captureTask(OnyxCache_1.TASK.CLEAR, promise);
|
|
406
|
-
});
|
|
487
|
+
}));
|
|
407
488
|
}
|
|
408
489
|
/**
|
|
409
490
|
* Insert API responses and lifecycle data into Onyx
|
|
@@ -412,7 +493,17 @@ function clear(keysToPreserve = []) {
|
|
|
412
493
|
* @returns resolves when all operations are complete
|
|
413
494
|
*/
|
|
414
495
|
function update(data) {
|
|
415
|
-
return
|
|
496
|
+
return PendingWrites_1.default.trackPendingWrite(
|
|
497
|
+
// multiSet/collection items keep their keys in `.value`, not `.key`.
|
|
498
|
+
data.flatMap((updateItem) => {
|
|
499
|
+
var _a;
|
|
500
|
+
if (updateItem.onyxMethod === OnyxUtils_1.default.METHOD.MULTI_SET ||
|
|
501
|
+
updateItem.onyxMethod === OnyxUtils_1.default.METHOD.MERGE_COLLECTION ||
|
|
502
|
+
updateItem.onyxMethod === OnyxUtils_1.default.METHOD.SET_COLLECTION) {
|
|
503
|
+
return Object.keys((_a = updateItem.value) !== null && _a !== void 0 ? _a : {});
|
|
504
|
+
}
|
|
505
|
+
return updateItem.key;
|
|
506
|
+
}), OnyxUtils_1.default.afterInit(() => {
|
|
416
507
|
// The queue of operations within a single `update` call in the format of <item key - list of operations updating the item>.
|
|
417
508
|
// This allows us to batch the operations per item and merge them into one operation in the order they were requested.
|
|
418
509
|
const updateQueue = {};
|
|
@@ -536,7 +627,7 @@ function update(data) {
|
|
|
536
627
|
// We need to run the snapshot updates before the other updates so the snapshot data can be updated before the loading state in the snapshot
|
|
537
628
|
const finalPromises = snapshotPromises.concat(promises);
|
|
538
629
|
return clearPromise.then(() => Promise.all(finalPromises.map((p) => p()))).then(() => undefined);
|
|
539
|
-
});
|
|
630
|
+
}));
|
|
540
631
|
}
|
|
541
632
|
/**
|
|
542
633
|
* Sets a collection by replacing all existing collection members with new values.
|
|
@@ -552,7 +643,7 @@ function update(data) {
|
|
|
552
643
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
553
644
|
*/
|
|
554
645
|
function setCollection(collectionKey, collection) {
|
|
555
|
-
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection }));
|
|
646
|
+
return PendingWrites_1.default.trackPendingWrite(Object.keys(collection), OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection })));
|
|
556
647
|
}
|
|
557
648
|
/**
|
|
558
649
|
* Returns persisted Onyx key-value pairs as a plain object.
|
package/dist/OnyxCache.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValueOf } from 'type-fest';
|
|
2
|
-
import type { OnyxKey, OnyxValue } from './types';
|
|
2
|
+
import type { KeyValueMapping, OnyxCollection, OnyxKey, OnyxValue } from './types';
|
|
3
3
|
declare const TASK: {
|
|
4
4
|
readonly GET: "get";
|
|
5
5
|
readonly GET_ALL_KEYS: "getAllKeys";
|
|
@@ -151,7 +151,7 @@ declare class OnyxCache {
|
|
|
151
151
|
* Returns a frozen snapshot with structural sharing — safe to return by reference.
|
|
152
152
|
* Lazily rebuilds the snapshot if the collection was modified since the last read.
|
|
153
153
|
*/
|
|
154
|
-
getCollectionData(collectionKey: OnyxKey):
|
|
154
|
+
getCollectionData(collectionKey: OnyxKey): OnyxCollection<KeyValueMapping[OnyxKey]> | undefined;
|
|
155
155
|
}
|
|
156
156
|
declare const instance: OnyxCache;
|
|
157
157
|
export default instance;
|
package/dist/OnyxCache.js
CHANGED
|
@@ -437,18 +437,13 @@ class OnyxCache {
|
|
|
437
437
|
this.dirtyCollections.delete(collectionKey);
|
|
438
438
|
}
|
|
439
439
|
const snapshot = this.collectionSnapshots.get(collectionKey);
|
|
440
|
-
//
|
|
440
|
+
// Returning `undefined` for this collection key means init hasn't seeded it yet (pre-load), so there's
|
|
441
|
+
// nothing to return. `setCollectionKeys()` (called inside `Onyx.init`) seeds every
|
|
442
|
+
// known collection with a frozen empty entry, so the presence of an entry is the reliable
|
|
443
|
+
// post-init "loaded" signal.
|
|
441
444
|
if (snapshot === undefined) {
|
|
442
445
|
return undefined;
|
|
443
446
|
}
|
|
444
|
-
// The collection is empty (it holds our shared empty object). But "empty" is ambiguous
|
|
445
|
-
// during startup: we can't tell an actually-empty collection apart from one whose data
|
|
446
|
-
// hasn't loaded yet. Once any key exists, we know setAllKeys has run and loaded everything,
|
|
447
|
-
// so an empty collection really is empty. Before that, return undefined so subscribers
|
|
448
|
-
// don't briefly see a collection as empty when it just hasn't loaded.
|
|
449
|
-
if (snapshot === FROZEN_EMPTY_COLLECTION) {
|
|
450
|
-
return this.storageKeys.size > 0 ? FROZEN_EMPTY_COLLECTION : undefined;
|
|
451
|
-
}
|
|
452
447
|
return snapshot;
|
|
453
448
|
}
|
|
454
449
|
}
|