react-native-onyx 3.0.93 → 3.0.95
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/OnyxCache.js +20 -10
- package/dist/createMemoizedSelector.d.ts +18 -0
- package/dist/createMemoizedSelector.js +37 -0
- package/dist/storage/providers/IDBKeyValProvider/classifyError.js +6 -0
- package/dist/useOnyx.d.ts +1 -2
- package/dist/useOnyx.js +6 -49
- package/package.json +1 -1
- package/dist/useLiveRef.d.ts +0 -9
- package/dist/useLiveRef.js +0 -16
package/dist/OnyxCache.js
CHANGED
|
@@ -286,7 +286,7 @@ class OnyxCache {
|
|
|
286
286
|
// Initialize frozen snapshots for collection keys
|
|
287
287
|
for (const collectionKey of collectionKeys) {
|
|
288
288
|
if (!this.collectionSnapshots.has(collectionKey)) {
|
|
289
|
-
this.collectionSnapshots.set(collectionKey,
|
|
289
|
+
this.collectionSnapshots.set(collectionKey, FROZEN_EMPTY_COLLECTION);
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
}
|
|
@@ -302,6 +302,7 @@ class OnyxCache {
|
|
|
302
302
|
const previousSnapshot = this.collectionSnapshots.get(collectionKey);
|
|
303
303
|
const members = {};
|
|
304
304
|
let hasMemberChanges = false;
|
|
305
|
+
let hasMembers = false;
|
|
305
306
|
// Use the indexed forward lookup for O(collectionMembers) iteration.
|
|
306
307
|
// Falls back to scanning all storageKeys if the index isn't populated yet.
|
|
307
308
|
const memberKeys = OnyxKeys_1.default.getMembersOfCollection(collectionKey);
|
|
@@ -318,6 +319,7 @@ class OnyxCache {
|
|
|
318
319
|
// and should not be included in the frozen collection snapshot.
|
|
319
320
|
if (val !== undefined && val !== null) {
|
|
320
321
|
members[key] = val;
|
|
322
|
+
hasMembers = true;
|
|
321
323
|
// Check if this member's reference changed from the old snapshot
|
|
322
324
|
if (!hasMemberChanges && (!previousSnapshot || previousSnapshot[key] !== val)) {
|
|
323
325
|
hasMemberChanges = true;
|
|
@@ -342,6 +344,13 @@ class OnyxCache {
|
|
|
342
344
|
if (!hasMemberChanges && previousSnapshot) {
|
|
343
345
|
return;
|
|
344
346
|
}
|
|
347
|
+
// When the collection has no members, reuse one shared empty object for every empty
|
|
348
|
+
// collection. That way reads can tell a collection is empty with a quick `===` check
|
|
349
|
+
// instead of looping over its keys every time.
|
|
350
|
+
if (!hasMembers) {
|
|
351
|
+
this.collectionSnapshots.set(collectionKey, FROZEN_EMPTY_COLLECTION);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
345
354
|
Object.freeze(members);
|
|
346
355
|
this.collectionSnapshots.set(collectionKey, members);
|
|
347
356
|
}
|
|
@@ -356,17 +365,18 @@ class OnyxCache {
|
|
|
356
365
|
this.dirtyCollections.delete(collectionKey);
|
|
357
366
|
}
|
|
358
367
|
const snapshot = this.collectionSnapshots.get(collectionKey);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// "init complete, this collection is genuinely empty" from "init not done yet."
|
|
362
|
-
// During init, setAllKeys loads ALL keys at once — so if any key exists,
|
|
363
|
-
// the full storage picture is loaded and an empty collection is truly empty.
|
|
364
|
-
// Returning undefined before init prevents subscribers from seeing a false empty state.
|
|
365
|
-
if (this.storageKeys.size > 0) {
|
|
366
|
-
return FROZEN_EMPTY_COLLECTION;
|
|
367
|
-
}
|
|
368
|
+
// We never stored anything for this collection key.
|
|
369
|
+
if (snapshot === undefined) {
|
|
368
370
|
return undefined;
|
|
369
371
|
}
|
|
372
|
+
// The collection is empty (it holds our shared empty object). But "empty" is ambiguous
|
|
373
|
+
// during startup: we can't tell an actually-empty collection apart from one whose data
|
|
374
|
+
// hasn't loaded yet. Once any key exists, we know setAllKeys has run and loaded everything,
|
|
375
|
+
// so an empty collection really is empty. Before that, return undefined so subscribers
|
|
376
|
+
// don't briefly see a collection as empty when it just hasn't loaded.
|
|
377
|
+
if (snapshot === FROZEN_EMPTY_COLLECTION) {
|
|
378
|
+
return this.storageKeys.size > 0 ? FROZEN_EMPTY_COLLECTION : undefined;
|
|
379
|
+
}
|
|
370
380
|
return snapshot;
|
|
371
381
|
}
|
|
372
382
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a selector function so that:
|
|
3
|
+
* - Calling the wrapper with the same input reference twice short-circuits to the cached output
|
|
4
|
+
* (cheap `===` check, no recompute).
|
|
5
|
+
* - Calling with a different input that produces a deep-equal output returns the *previous*
|
|
6
|
+
* output reference, so downstream `===` comparisons treat it as unchanged.
|
|
7
|
+
*
|
|
8
|
+
* This is the minimum needed for `useSyncExternalStore` to not loop when consumers pass
|
|
9
|
+
* inline selectors that allocate fresh objects on every call (e.g. `(e) => ({id: e?.id})`):
|
|
10
|
+
* without the deep-equal fallback, every `getSnapshot` would return a new reference and React
|
|
11
|
+
* would re-render (or throw "getSnapshot should be cached") indefinitely.
|
|
12
|
+
*
|
|
13
|
+
* Stateful by design — each call to `createMemoizedSelector` produces an independent wrapper
|
|
14
|
+
* with its own `lastInput`/`lastOutput` cache, so a wrapper must not be shared across
|
|
15
|
+
* subscriptions that can see different inputs.
|
|
16
|
+
*/
|
|
17
|
+
declare function createMemoizedSelector<TInput, TOutput>(selector: (input: TInput) => TOutput): (input: TInput) => TOutput;
|
|
18
|
+
export default createMemoizedSelector;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fast_equals_1 = require("fast-equals");
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a selector function so that:
|
|
6
|
+
* - Calling the wrapper with the same input reference twice short-circuits to the cached output
|
|
7
|
+
* (cheap `===` check, no recompute).
|
|
8
|
+
* - Calling with a different input that produces a deep-equal output returns the *previous*
|
|
9
|
+
* output reference, so downstream `===` comparisons treat it as unchanged.
|
|
10
|
+
*
|
|
11
|
+
* This is the minimum needed for `useSyncExternalStore` to not loop when consumers pass
|
|
12
|
+
* inline selectors that allocate fresh objects on every call (e.g. `(e) => ({id: e?.id})`):
|
|
13
|
+
* without the deep-equal fallback, every `getSnapshot` would return a new reference and React
|
|
14
|
+
* would re-render (or throw "getSnapshot should be cached") indefinitely.
|
|
15
|
+
*
|
|
16
|
+
* Stateful by design — each call to `createMemoizedSelector` produces an independent wrapper
|
|
17
|
+
* with its own `lastInput`/`lastOutput` cache, so a wrapper must not be shared across
|
|
18
|
+
* subscriptions that can see different inputs.
|
|
19
|
+
*/
|
|
20
|
+
function createMemoizedSelector(selector) {
|
|
21
|
+
let lastInput;
|
|
22
|
+
let lastOutput;
|
|
23
|
+
let hasComputed = false;
|
|
24
|
+
return (input) => {
|
|
25
|
+
if (hasComputed && lastInput === input) {
|
|
26
|
+
return lastOutput;
|
|
27
|
+
}
|
|
28
|
+
const next = selector(input);
|
|
29
|
+
lastInput = input;
|
|
30
|
+
if (!hasComputed || !(0, fast_equals_1.deepEqual)(lastOutput, next)) {
|
|
31
|
+
lastOutput = next;
|
|
32
|
+
hasComputed = true;
|
|
33
|
+
}
|
|
34
|
+
return lastOutput;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
exports.default = createMemoizedSelector;
|
|
@@ -12,6 +12,12 @@ function classifyIDBError(error) {
|
|
|
12
12
|
if (message.includes("failed to execute 'put' on 'idbobjectstore'")) {
|
|
13
13
|
return errors_1.StorageErrorClass.INVALID_DATA;
|
|
14
14
|
}
|
|
15
|
+
// A queued File/Blob whose backing bytes are gone — the source OS file was modified, deleted, or
|
|
16
|
+
// renamed after being picked, so the structured clone fails at write time. Chromium reports it as
|
|
17
|
+
// InvalidBlob (Windows) or IOError (macOS). Retrying re-reads the same dead blob and can never succeed.
|
|
18
|
+
if (message.includes('failed to write blobs')) {
|
|
19
|
+
return errors_1.StorageErrorClass.INVALID_DATA;
|
|
20
|
+
}
|
|
15
21
|
// Browser quota exceeded.
|
|
16
22
|
if (name.includes('quotaexceedederror') || message.includes('quotaexceedederror')) {
|
|
17
23
|
return errors_1.StorageErrorClass.CAPACITY;
|
package/dist/useOnyx.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DependencyList } from 'react';
|
|
2
1
|
import type { OnyxKey, OnyxValue } from './types';
|
|
3
2
|
type UseOnyxSelector<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>> = (data: OnyxValue<TKey> | undefined) => TReturnValue;
|
|
4
3
|
type UseOnyxOptions<TKey extends OnyxKey, TReturnValue> = {
|
|
@@ -21,6 +20,6 @@ type ResultMetadata = {
|
|
|
21
20
|
status: FetchStatus;
|
|
22
21
|
};
|
|
23
22
|
type UseOnyxResult<TValue> = [NonNullable<TValue> | undefined, ResultMetadata];
|
|
24
|
-
declare function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey, options?: UseOnyxOptions<TKey, TReturnValue
|
|
23
|
+
declare function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey, options?: UseOnyxOptions<TKey, TReturnValue>): UseOnyxResult<TReturnValue>;
|
|
25
24
|
export default useOnyx;
|
|
26
25
|
export type { FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions, UseOnyxSelector };
|
package/dist/useOnyx.js
CHANGED
|
@@ -36,46 +36,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const fast_equals_1 = require("fast-equals");
|
|
40
39
|
const react_1 = require("react");
|
|
40
|
+
const createMemoizedSelector_1 = __importDefault(require("./createMemoizedSelector"));
|
|
41
41
|
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 OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache"));
|
|
45
45
|
const memoizedShallowEqual_1 = __importDefault(require("./memoizedShallowEqual"));
|
|
46
|
-
|
|
47
|
-
function useOnyx(key, options, dependencies = []) {
|
|
46
|
+
function useOnyx(key, options) {
|
|
48
47
|
const connectionRef = (0, react_1.useRef)(null);
|
|
49
|
-
const currentDependenciesRef = (0, useLiveRef_1.default)(dependencies);
|
|
50
48
|
const selector = options === null || options === void 0 ? void 0 : options.selector;
|
|
51
|
-
// Create memoized version of selector for performance
|
|
49
|
+
// Create memoized version of selector for performance. It caches by input reference
|
|
50
|
+
// with a deepEqual fallback on the output to keep the returned reference stable.
|
|
52
51
|
const memoizedSelector = (0, react_1.useMemo)(() => {
|
|
53
52
|
if (!selector) {
|
|
54
53
|
return null;
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let lastDependencies = [];
|
|
59
|
-
let hasComputed = false;
|
|
60
|
-
return (input) => {
|
|
61
|
-
const currentDependencies = currentDependenciesRef.current;
|
|
62
|
-
// Recompute if input changed, dependencies changed, or first time
|
|
63
|
-
const dependenciesChanged = !(0, fast_equals_1.shallowEqual)(lastDependencies, currentDependencies);
|
|
64
|
-
if (!hasComputed || lastInput !== input || dependenciesChanged) {
|
|
65
|
-
const newOutput = selector(input);
|
|
66
|
-
// Always track the current input to avoid re-running the selector
|
|
67
|
-
// when the same input is seen again (even if the output didn't change).
|
|
68
|
-
lastInput = input;
|
|
69
|
-
// Only update the output reference if it actually changed
|
|
70
|
-
if (!hasComputed || !(0, fast_equals_1.deepEqual)(lastOutput, newOutput) || dependenciesChanged) {
|
|
71
|
-
lastOutput = newOutput;
|
|
72
|
-
lastDependencies = [...currentDependencies];
|
|
73
|
-
hasComputed = true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return lastOutput;
|
|
77
|
-
};
|
|
78
|
-
}, [currentDependenciesRef, selector]);
|
|
55
|
+
return (0, createMemoizedSelector_1.default)(selector);
|
|
56
|
+
}, [selector]);
|
|
79
57
|
// Stores the previous cached value as it's necessary to compare with the new value in `getSnapshot()`.
|
|
80
58
|
// We initialize it to `null` to simulate that we don't have any value from cache yet.
|
|
81
59
|
const previousValueRef = (0, react_1.useRef)(null);
|
|
@@ -109,27 +87,6 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
109
87
|
selector: options === null || options === void 0 ? void 0 : options.selector,
|
|
110
88
|
}), [key, options === null || options === void 0 ? void 0 : options.selector]);
|
|
111
89
|
(0, react_1.useEffect)(() => () => OnyxSnapshotCache_1.default.deregisterConsumer(key, cacheKey), [key, cacheKey]);
|
|
112
|
-
// Track previous dependencies to prevent infinite loops
|
|
113
|
-
const previousDependenciesRef = (0, react_1.useRef)([]);
|
|
114
|
-
(0, react_1.useEffect)(() => {
|
|
115
|
-
// This effect will only run if the `dependencies` array changes. If it changes it will force the hook
|
|
116
|
-
// to trigger a `getSnapshot()` update by calling the stored `onStoreChange()` function reference, thus
|
|
117
|
-
// re-running the hook and returning the latest value to the consumer.
|
|
118
|
-
// Deep equality check to prevent infinite loops when dependencies array reference changes
|
|
119
|
-
// but content remains the same
|
|
120
|
-
if ((0, fast_equals_1.shallowEqual)(previousDependenciesRef.current, dependencies)) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
previousDependenciesRef.current = dependencies;
|
|
124
|
-
if (connectionRef.current === null || isConnectingRef.current || connectedKeyRef.current !== key || !onStoreChangeFnRef.current) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// Invalidate cache when dependencies change so selector runs with new closure values
|
|
128
|
-
OnyxSnapshotCache_1.default.invalidateForKey(key);
|
|
129
|
-
shouldGetCachedValueRef.current = true;
|
|
130
|
-
onStoreChangeFnRef.current();
|
|
131
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
|
-
}, [...dependencies]);
|
|
133
90
|
// Tracks the last memoizedSelector reference that getSnapshot() has computed with.
|
|
134
91
|
// When the selector changes, this mismatch forces getSnapshot() to re-evaluate
|
|
135
92
|
// even if all other conditions (isFirstConnection, shouldGetCachedValue, key) are false.
|
package/package.json
CHANGED
package/dist/useLiveRef.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a mutable reference to a value, useful when you need to
|
|
3
|
-
* maintain a reference to a value that may change over time without triggering re-renders.
|
|
4
|
-
*
|
|
5
|
-
* @deprecated This hook breaks the Rules of React, and should not be used.
|
|
6
|
-
* The migration effort to remove it safely is not currently planned.
|
|
7
|
-
*/
|
|
8
|
-
declare function useLiveRef<T>(value: T): import("react").MutableRefObject<T>;
|
|
9
|
-
export default useLiveRef;
|
package/dist/useLiveRef.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const react_1 = require("react");
|
|
4
|
-
/**
|
|
5
|
-
* Creates a mutable reference to a value, useful when you need to
|
|
6
|
-
* maintain a reference to a value that may change over time without triggering re-renders.
|
|
7
|
-
*
|
|
8
|
-
* @deprecated This hook breaks the Rules of React, and should not be used.
|
|
9
|
-
* The migration effort to remove it safely is not currently planned.
|
|
10
|
-
*/
|
|
11
|
-
function useLiveRef(value) {
|
|
12
|
-
const ref = (0, react_1.useRef)(value);
|
|
13
|
-
ref.current = value;
|
|
14
|
-
return ref;
|
|
15
|
-
}
|
|
16
|
-
exports.default = useLiveRef;
|