react-native-onyx 3.0.57 → 3.0.59
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/dist/useOnyx.js +25 -16
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +25 -10
- package/package.json +1 -1
- package/dist/usePrevious.d.ts +0 -5
- package/dist/usePrevious.js +0 -14
package/dist/useOnyx.js
CHANGED
|
@@ -42,12 +42,10 @@ const OnyxCache_1 = __importStar(require("./OnyxCache"));
|
|
|
42
42
|
const OnyxConnectionManager_1 = __importDefault(require("./OnyxConnectionManager"));
|
|
43
43
|
const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
|
|
44
44
|
const OnyxKeys_1 = __importDefault(require("./OnyxKeys"));
|
|
45
|
-
const usePrevious_1 = __importDefault(require("./usePrevious"));
|
|
46
45
|
const OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache"));
|
|
47
46
|
const useLiveRef_1 = __importDefault(require("./useLiveRef"));
|
|
48
47
|
function useOnyx(key, options, dependencies = []) {
|
|
49
48
|
const connectionRef = (0, react_1.useRef)(null);
|
|
50
|
-
const previousKey = (0, usePrevious_1.default)(key);
|
|
51
49
|
const currentDependenciesRef = (0, useLiveRef_1.default)(dependencies);
|
|
52
50
|
const selector = options === null || options === void 0 ? void 0 : options.selector;
|
|
53
51
|
// Create memoized version of selector for performance
|
|
@@ -93,9 +91,12 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
93
91
|
status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading',
|
|
94
92
|
},
|
|
95
93
|
]);
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
94
|
+
// Tracks which key has completed its first Onyx connection callback. When this doesn't match the
|
|
95
|
+
// current key, getSnapshot() treats the hook as being in its "first connection" state for that key.
|
|
96
|
+
// This is key-aware by design: when the key changes, connectedKeyRef still holds the old key (or null
|
|
97
|
+
// after cleanup), so the hook automatically enters first-connection mode for the new key without any
|
|
98
|
+
// explicit reset logic — eliminating the race condition where cleanup could clobber a boolean flag.
|
|
99
|
+
const connectedKeyRef = (0, react_1.useRef)(null);
|
|
99
100
|
// Indicates if the hook is connecting to an Onyx key.
|
|
100
101
|
const isConnectingRef = (0, react_1.useRef)(false);
|
|
101
102
|
// Stores the `onStoreChange()` function, which can be used to trigger a `getSnapshot()` update when desired.
|
|
@@ -122,7 +123,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
125
|
previousDependenciesRef.current = dependencies;
|
|
125
|
-
if (connectionRef.current === null || isConnectingRef.current || !onStoreChangeFnRef.current) {
|
|
126
|
+
if (connectionRef.current === null || isConnectingRef.current || connectedKeyRef.current !== key || !onStoreChangeFnRef.current) {
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
// Invalidate cache when dependencies change so selector runs with new closure values
|
|
@@ -154,7 +155,8 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
154
155
|
// Check if we have any cache for this Onyx key
|
|
155
156
|
// Don't use cache for first connection with initWithStoredValues: false
|
|
156
157
|
// Also don't use cache during active data updates (when shouldGetCachedValueRef is true)
|
|
157
|
-
|
|
158
|
+
const isFirstConnection = connectedKeyRef.current !== key;
|
|
159
|
+
if (!(isFirstConnection && (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false) && !shouldGetCachedValueRef.current) {
|
|
158
160
|
const cachedResult = OnyxSnapshotCache_1.default.getCachedResult(key, cacheKey);
|
|
159
161
|
if (cachedResult !== undefined) {
|
|
160
162
|
resultRef.current = cachedResult;
|
|
@@ -162,7 +164,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
// We return the initial result right away during the first connection if `initWithStoredValues` is set to `false`.
|
|
165
|
-
if (
|
|
167
|
+
if (isFirstConnection && (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false) {
|
|
166
168
|
const result = resultRef.current;
|
|
167
169
|
// Store result in snapshot cache
|
|
168
170
|
OnyxSnapshotCache_1.default.setCachedResult(key, cacheKey, result);
|
|
@@ -172,7 +174,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
172
174
|
// so we can return any cached value right away. For the case where the key has changed, If we don't return the cached value right away, then the UI will show the incorrect (previous) value for a brief period which looks like a UI glitch to the user. After the connection is made, we only
|
|
173
175
|
// update `newValueRef` when `Onyx.connect()` callback is fired.
|
|
174
176
|
const hasSelectorChanged = lastComputedSelectorRef.current !== memoizedSelector;
|
|
175
|
-
if (
|
|
177
|
+
if (isFirstConnection || shouldGetCachedValueRef.current || hasSelectorChanged) {
|
|
176
178
|
// Gets the value from cache and maps it with selector. It changes `null` to `undefined` for `useOnyx` compatibility.
|
|
177
179
|
const value = OnyxUtils_1.default.tryGetCachedValue(key);
|
|
178
180
|
const selectedValue = memoizedSelector ? memoizedSelector(value) : value;
|
|
@@ -187,7 +189,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
187
189
|
let newFetchStatus;
|
|
188
190
|
// If we have pending merge operations for the key during the first connection, we set the new value to `undefined`
|
|
189
191
|
// and fetch status to `loading` to simulate that it is still being loaded until we have the most updated data.
|
|
190
|
-
if (
|
|
192
|
+
if (isFirstConnection && OnyxUtils_1.default.hasPendingMergeForKey(key)) {
|
|
191
193
|
newValueRef.current = undefined;
|
|
192
194
|
newFetchStatus = 'loading';
|
|
193
195
|
}
|
|
@@ -211,7 +213,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
211
213
|
// OR we have a pending `Onyx.clear()` task (if `Onyx.clear()` is running cache might not be available anymore
|
|
212
214
|
// OR the subscriber is triggered (the value is gotten from the storage)
|
|
213
215
|
// so we update the cached value/result right away in order to prevent infinite loading state issues).
|
|
214
|
-
const shouldUpdateResult = !areValuesEqual || (previousValueRef.current === null && (hasCacheForKey || OnyxCache_1.default.hasPendingTask(OnyxCache_1.TASK.CLEAR) || !
|
|
216
|
+
const shouldUpdateResult = !areValuesEqual || (previousValueRef.current === null && (hasCacheForKey || OnyxCache_1.default.hasPendingTask(OnyxCache_1.TASK.CLEAR) || !isFirstConnection));
|
|
215
217
|
if (shouldUpdateResult) {
|
|
216
218
|
previousValueRef.current = newValueRef.current;
|
|
217
219
|
// If the new value is `null` we default it to `undefined` to ensure the consumer gets a consistent result from the hook.
|
|
@@ -228,8 +230,15 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
228
230
|
OnyxSnapshotCache_1.default.setCachedResult(key, cacheKey, resultRef.current);
|
|
229
231
|
}
|
|
230
232
|
return resultRef.current;
|
|
231
|
-
}, [options === null || options === void 0 ? void 0 : options.initWithStoredValues, key, memoizedSelector, cacheKey
|
|
233
|
+
}, [options === null || options === void 0 ? void 0 : options.initWithStoredValues, key, memoizedSelector, cacheKey]);
|
|
232
234
|
const subscribe = (0, react_1.useCallback)((onStoreChange) => {
|
|
235
|
+
// Reset internal state so the hook properly transitions through loading
|
|
236
|
+
// for the new key instead of preserving stale state from the previous one.
|
|
237
|
+
previousValueRef.current = null;
|
|
238
|
+
newValueRef.current = null;
|
|
239
|
+
shouldGetCachedValueRef.current = true;
|
|
240
|
+
sourceValueRef.current = undefined;
|
|
241
|
+
resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }];
|
|
233
242
|
isConnectingRef.current = true;
|
|
234
243
|
onStoreChangeFnRef.current = onStoreChange;
|
|
235
244
|
connectionRef.current = OnyxConnectionManager_1.default.connect({
|
|
@@ -237,9 +246,9 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
237
246
|
callback: (value, callbackKey, sourceValue) => {
|
|
238
247
|
isConnectingRef.current = false;
|
|
239
248
|
onStoreChangeFnRef.current = onStoreChange;
|
|
240
|
-
// Signals that the first connection was made, so some logics
|
|
241
|
-
// won't be executed anymore.
|
|
242
|
-
|
|
249
|
+
// Signals that the first connection was made for this key, so some logics
|
|
250
|
+
// in `getSnapshot()` won't be executed anymore.
|
|
251
|
+
connectedKeyRef.current = key;
|
|
243
252
|
// Signals that we want to get the newest cached value again in `getSnapshot()`.
|
|
244
253
|
shouldGetCachedValueRef.current = true;
|
|
245
254
|
// sourceValue is unknown type, so we need to cast it to the correct type.
|
|
@@ -259,7 +268,7 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
259
268
|
return;
|
|
260
269
|
}
|
|
261
270
|
OnyxConnectionManager_1.default.disconnect(connectionRef.current);
|
|
262
|
-
|
|
271
|
+
connectedKeyRef.current = null;
|
|
263
272
|
isConnectingRef.current = false;
|
|
264
273
|
onStoreChangeFnRef.current = null;
|
|
265
274
|
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ type FastMergeResult<TValue> = {
|
|
|
37
37
|
declare function fastMerge<TValue>(target: TValue, source: TValue, options?: FastMergeOptions, metadata?: FastMergeMetadata, basePath?: string[]): FastMergeResult<TValue>;
|
|
38
38
|
/** Checks whether the given object is an object and not null/undefined. */
|
|
39
39
|
declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
|
|
40
|
-
/** Deep removes the nested null values from the given value. */
|
|
40
|
+
/** Deep removes the nested null values from the given value. Returns the original reference if no nulls were found. */
|
|
41
41
|
declare function removeNestedNullValues<TValue extends OnyxInput<OnyxKey> | null>(value: TValue): TValue;
|
|
42
42
|
/** Formats the action name by uppercasing and adding the key if provided. */
|
|
43
43
|
declare function formatActionName(method: string, key?: OnyxKey): string;
|
package/dist/utils.js
CHANGED
|
@@ -39,6 +39,9 @@ function fastMerge(target, source, options, metadata, basePath = []) {
|
|
|
39
39
|
function mergeObject(target, source, options, metadata, basePath) {
|
|
40
40
|
const destination = {};
|
|
41
41
|
const targetObject = isMergeableObject(target) ? target : undefined;
|
|
42
|
+
// Track whether the merge actually changed anything compared to target.
|
|
43
|
+
// If nothing changed, we return the original target reference for reference stability.
|
|
44
|
+
let hasChanged = !targetObject;
|
|
42
45
|
// First we want to copy over all keys from the target into the destination object,
|
|
43
46
|
// in case "target" is a mergable object.
|
|
44
47
|
// If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object
|
|
@@ -51,6 +54,7 @@ function mergeObject(target, source, options, metadata, basePath) {
|
|
|
51
54
|
// If either the source or target value is null, we want to omit the key from the merged object.
|
|
52
55
|
const shouldOmitNullishProperty = options.shouldRemoveNestedNulls && (targetProperty === null || sourceProperty === null);
|
|
53
56
|
if (targetProperty === undefined || shouldOmitNullishProperty) {
|
|
57
|
+
hasChanged = true;
|
|
54
58
|
continue;
|
|
55
59
|
}
|
|
56
60
|
destination[key] = targetProperty;
|
|
@@ -68,6 +72,9 @@ function mergeObject(target, source, options, metadata, basePath) {
|
|
|
68
72
|
}
|
|
69
73
|
// If the source value is not a mergable object, we need to set the key directly.
|
|
70
74
|
if (!isMergeableObject(sourceProperty)) {
|
|
75
|
+
if (destination[key] !== sourceProperty) {
|
|
76
|
+
hasChanged = true;
|
|
77
|
+
}
|
|
71
78
|
destination[key] = sourceProperty;
|
|
72
79
|
continue;
|
|
73
80
|
}
|
|
@@ -76,6 +83,7 @@ function mergeObject(target, source, options, metadata, basePath) {
|
|
|
76
83
|
// To achieve this, we first mark these nested objects with an internal flag.
|
|
77
84
|
// When calling fastMerge again with "mark" removal mode, the marked objects will be removed.
|
|
78
85
|
if (options.objectRemovalMode === 'mark' && targetProperty === null) {
|
|
86
|
+
hasChanged = true;
|
|
79
87
|
targetProperty = { [ONYX_INTERNALS__REPLACE_OBJECT_MARK]: true };
|
|
80
88
|
metadata.replaceNullPatches.push([[...basePath, key], Object.assign({}, sourceProperty)]);
|
|
81
89
|
}
|
|
@@ -83,6 +91,7 @@ function mergeObject(target, source, options, metadata, basePath) {
|
|
|
83
91
|
// has the internal flag set, we replace the entire destination object with the source one and remove
|
|
84
92
|
// the flag.
|
|
85
93
|
if (options.objectRemovalMode === 'replace' && sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) {
|
|
94
|
+
hasChanged = true;
|
|
86
95
|
// We do a spread here in order to have a new object reference and allow us to delete the internal flag
|
|
87
96
|
// of the merged object only.
|
|
88
97
|
const sourcePropertyWithoutMark = Object.assign({}, sourceProperty);
|
|
@@ -90,9 +99,13 @@ function mergeObject(target, source, options, metadata, basePath) {
|
|
|
90
99
|
destination[key] = sourcePropertyWithoutMark;
|
|
91
100
|
continue;
|
|
92
101
|
}
|
|
93
|
-
|
|
102
|
+
const merged = fastMerge(targetProperty, sourceProperty, options, metadata, [...basePath, key]).result;
|
|
103
|
+
if (merged !== targetProperty) {
|
|
104
|
+
hasChanged = true;
|
|
105
|
+
}
|
|
106
|
+
destination[key] = merged;
|
|
94
107
|
}
|
|
95
|
-
return destination;
|
|
108
|
+
return hasChanged ? destination : targetObject;
|
|
96
109
|
}
|
|
97
110
|
/** Checks whether the given object is an object and not null/undefined. */
|
|
98
111
|
function isEmptyObject(obj) {
|
|
@@ -106,30 +119,32 @@ function isMergeableObject(value) {
|
|
|
106
119
|
const isNonNullObject = value != null ? typeof value === 'object' : false;
|
|
107
120
|
return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value);
|
|
108
121
|
}
|
|
109
|
-
/** Deep removes the nested null values from the given value. */
|
|
122
|
+
/** Deep removes the nested null values from the given value. Returns the original reference if no nulls were found. */
|
|
110
123
|
function removeNestedNullValues(value) {
|
|
111
|
-
if (value === null || value === undefined || typeof value !== 'object') {
|
|
124
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
|
|
112
125
|
return value;
|
|
113
126
|
}
|
|
114
|
-
|
|
115
|
-
return [...value];
|
|
116
|
-
}
|
|
127
|
+
let hasChanged = false;
|
|
117
128
|
const result = {};
|
|
118
129
|
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
119
130
|
for (const key in value) {
|
|
120
131
|
const propertyValue = value[key];
|
|
121
132
|
if (propertyValue === null || propertyValue === undefined) {
|
|
133
|
+
hasChanged = true;
|
|
122
134
|
continue;
|
|
123
135
|
}
|
|
124
136
|
if (typeof propertyValue === 'object' && !Array.isArray(propertyValue)) {
|
|
125
|
-
const
|
|
126
|
-
|
|
137
|
+
const cleaned = removeNestedNullValues(propertyValue);
|
|
138
|
+
if (cleaned !== propertyValue) {
|
|
139
|
+
hasChanged = true;
|
|
140
|
+
}
|
|
141
|
+
result[key] = cleaned;
|
|
127
142
|
}
|
|
128
143
|
else {
|
|
129
144
|
result[key] = propertyValue;
|
|
130
145
|
}
|
|
131
146
|
}
|
|
132
|
-
return result;
|
|
147
|
+
return hasChanged ? result : value;
|
|
133
148
|
}
|
|
134
149
|
/** Formats the action name by uppercasing and adding the key if provided. */
|
|
135
150
|
function formatActionName(method, key) {
|
package/package.json
CHANGED
package/dist/usePrevious.d.ts
DELETED
package/dist/usePrevious.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const react_1 = require("react");
|
|
4
|
-
/**
|
|
5
|
-
* Returns the previous value of the provided value.
|
|
6
|
-
*/
|
|
7
|
-
function usePrevious(value) {
|
|
8
|
-
const ref = (0, react_1.useRef)(value);
|
|
9
|
-
(0, react_1.useEffect)(() => {
|
|
10
|
-
ref.current = value;
|
|
11
|
-
}, [value]);
|
|
12
|
-
return ref.current;
|
|
13
|
-
}
|
|
14
|
-
exports.default = usePrevious;
|