From f8bff5728d51c19876b52776d7378ee443be49f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 15 May 2026 11:38:00 +0100 Subject: [PATCH 1/2] Bump Onyx to 3.0.71 --- package-lock.json | 8 +-- package.json | 2 +- patches/react-native-onyx/details.md | 12 ++-- .../react-native-onyx+3.0.69.patch | 35 ---------- .../react-native-onyx+3.0.71.patch | 67 +++++++++++++++++++ 5 files changed, 80 insertions(+), 44 deletions(-) delete mode 100644 patches/react-native-onyx/react-native-onyx+3.0.69.patch create mode 100644 patches/react-native-onyx/react-native-onyx+3.0.71.patch diff --git a/package-lock.json b/package-lock.json index 7d036454b610..10c7b36ce719 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.35.0", "react-native-nitro-sqlite": "9.6.0", - "react-native-onyx": "3.0.69", + "react-native-onyx": "3.0.71", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34993,9 +34993,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.69", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.69.tgz", - "integrity": "sha512-J12iRFr4eXr1faolajLG47+P1JjToiju52+tcyyvFZB+9ik3FLiezbvz56udZd4TZi7M3mkRLuAMyzPv7BNE8w==", + "version": "3.0.71", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.71.tgz", + "integrity": "sha512-q84y7aULjoRtQMjLP7fXYtI0nDROyfCgSKHEgZNHBwSvSogOEbGbsHw9qGm6jFIjIoOJf6Hzd3+FnIrFu+WVEQ==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index dcf047214dec..96e669b1713a 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.35.0", "react-native-nitro-sqlite": "9.6.0", - "react-native-onyx": "3.0.69", + "react-native-onyx": "3.0.71", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", diff --git a/patches/react-native-onyx/details.md b/patches/react-native-onyx/details.md index b544adfc56b9..75e1d8488d1f 100644 --- a/patches/react-native-onyx/details.md +++ b/patches/react-native-onyx/details.md @@ -1,7 +1,11 @@ # `react-native-onyx` patches -### [react-native-onyx+3.0.69.patch](react-native-onyx+3.0.69.patch) +### [react-native-onyx+3.0.71.patch](react-native-onyx+3.0.71.patch) -- Reason: Onyx v3.0.59 ([PR #756](https://github.com/Expensify/react-native-onyx/pull/756)) added a state reset inside the `subscribe` callback of `useOnyx` to fix stale data when keys change dynamically. However, this reset runs unconditionally — including on initial mount — which causes `useSyncExternalStore` to see a new snapshot reference after subscription, triggering one extra render per `useOnyx` hook. This patch guards the reset with a `hasMountedRef` flag so it only runs on key-change re-subscriptions, not on initial mount. -- E/App issue: https://github.com/Expensify/App/issues/85416 -- Upstream PR/issue: N/A +- Reason: + + > Reverts [Onyx PR #770 (the subscription-side skip for skippable collection member ids in subscribeToKey)](https://github.com/Expensify/react-native-onyx/pull/770) and the line [PR #779](https://github.com/Expensify/react-native-onyx/pull/779) added to work around [PR #770](https://github.com/Expensify/react-native-onyx/pull/770)'s silent-no-callback contract. + +- Upstream PR/issue: TODO +- E/App issue: https://github.com/Expensify/App/issues/86181 +- PR Introducing Patch: TODO \ No newline at end of file diff --git a/patches/react-native-onyx/react-native-onyx+3.0.69.patch b/patches/react-native-onyx/react-native-onyx+3.0.69.patch deleted file mode 100644 index 7b321b0f51de..000000000000 --- a/patches/react-native-onyx/react-native-onyx+3.0.69.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/node_modules/react-native-onyx/dist/useOnyx.js b/node_modules/react-native-onyx/dist/useOnyx.js -index b361d0d..4df16fa 100644 ---- a/node_modules/react-native-onyx/dist/useOnyx.js -+++ b/node_modules/react-native-onyx/dist/useOnyx.js -@@ -97,6 +97,9 @@ function useOnyx(key, options, dependencies = []) { - // after cleanup), so the hook automatically enters first-connection mode for the new key without any - // explicit reset logic — eliminating the race condition where cleanup could clobber a boolean flag. - const connectedKeyRef = (0, react_1.useRef)(null); -+ // Tracks whether the hook has completed its initial mount subscription. -+ // Unlike connectedKeyRef (which gets nulled by cleanup), this persists across re-subscriptions. -+ const hasMountedRef = (0, react_1.useRef)(false); - // Indicates if the hook is connecting to an Onyx key. - const isConnectingRef = (0, react_1.useRef)(false); - // Stores the `onStoreChange()` function, which can be used to trigger a `getSnapshot()` update when desired. -@@ -220,11 +223,15 @@ function useOnyx(key, options, dependencies = []) { - const subscribe = (0, react_1.useCallback)((onStoreChange) => { - // Reset internal state so the hook properly transitions through loading - // for the new key instead of preserving stale state from the previous one. -- previousValueRef.current = null; -- newValueRef.current = null; -- shouldGetCachedValueRef.current = true; -- sourceValueRef.current = undefined; -- resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }]; -+ // Only reset when the key has actually changed (not on initial mount). -+ if (hasMountedRef.current) { -+ previousValueRef.current = null; -+ newValueRef.current = null; -+ shouldGetCachedValueRef.current = true; -+ sourceValueRef.current = undefined; -+ resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }]; -+ } -+ hasMountedRef.current = true; - isConnectingRef.current = true; - onStoreChangeFnRef.current = onStoreChange; - connectionRef.current = OnyxConnectionManager_1.default.connect({ diff --git a/patches/react-native-onyx/react-native-onyx+3.0.71.patch b/patches/react-native-onyx/react-native-onyx+3.0.71.patch new file mode 100644 index 000000000000..90666509eba5 --- /dev/null +++ b/patches/react-native-onyx/react-native-onyx+3.0.71.patch @@ -0,0 +1,67 @@ +diff --git a/node_modules/react-native-onyx/dist/OnyxUtils.js b/node_modules/react-native-onyx/dist/OnyxUtils.js +index de56f94..495d378 100644 +--- a/node_modules/react-native-onyx/dist/OnyxUtils.js ++++ b/node_modules/react-native-onyx/dist/OnyxUtils.js +@@ -868,24 +868,6 @@ function subscribeToKey(connectOptions) { + const subscriptionID = lastSubscriptionID++; + callbackToStateMapping[subscriptionID] = mapping; + callbackToStateMapping[subscriptionID].subscriptionID = subscriptionID; +- // If the subscriber is attempting to connect to a collection member whose ID is skippable (e.g. "undefined", "null", etc.) +- // we suppress wiring the subscription fully to avoid unnecessary callback emissions such as for "report_undefined". +- // We still return a valid subscriptionID so callers can disconnect safely. +- try { +- const skippableIDs = getSkippableCollectionMemberIDs(); +- if (skippableIDs.size) { +- const [, collectionMemberID] = OnyxKeys_1.default.splitCollectionMemberKey(mapping.key); +- if (skippableIDs.has(collectionMemberID)) { +- // Clean up the provisional mapping to avoid retaining unused subscribers. +- OnyxCache_1.default.addNullishStorageKey(mapping.key); +- delete callbackToStateMapping[subscriptionID]; +- return subscriptionID; +- } +- } +- } +- catch (e) { +- // Not a collection member key, proceed as usual. +- } + // 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 + // to avoid having to loop through all the Subscribers all the time (even when just one connection belongs to one key), + // We create a mapping from key to lists of subscriptionIDs to access the specific list of subscriptionIDs. +@@ -1394,12 +1376,6 @@ function logKeyChanged(onyxMethod, key, value, hasChanged) { + function logKeyRemoved(onyxMethod, key) { + Logger.logInfo(`${onyxMethod} called for key: ${key} => null passed, so key was removed`); + } +-/** +- * Getter - returns the callback to state mapping, useful in test environments. +- */ +-function getCallbackToStateMapping() { +- return callbackToStateMapping; +-} + /** + * Clear internal variables used in this file, useful in test environments. + */ +@@ -1458,6 +1434,5 @@ const OnyxUtils = { + setWithRetry, + multiSetWithRetry, + setCollectionWithRetry, +- getCallbackToStateMapping, + }; + exports.default = OnyxUtils; +diff --git a/node_modules/react-native-onyx/dist/useOnyx.js b/node_modules/react-native-onyx/dist/useOnyx.js +index 9213cff..2e48c73 100644 +--- a/node_modules/react-native-onyx/dist/useOnyx.js ++++ b/node_modules/react-native-onyx/dist/useOnyx.js +@@ -220,12 +220,8 @@ function useOnyx(key, options, dependencies = []) { + newValueRef.current = null; + sourceValueRef.current = undefined; + resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }]; ++ shouldGetCachedValueRef.current = true; + } +- // Force a cache re-read on every (re)subscription so any side effects from +- // subscribeToKey (e.g. addNullishStorageKey for skippable collection member ids) +- // are reflected in the next getSnapshot. Resetting this flag does not change +- // resultRef by itself, so it doesn't cause an extra mount render. +- shouldGetCachedValueRef.current = true; + hasMountedRef.current = true; + isConnectingRef.current = true; + onStoreChangeFnRef.current = onStoreChange; From 0033d7400cb6d3e8f4a87ffb75eb208fe616f610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 15 May 2026 12:01:28 +0100 Subject: [PATCH 2/2] Update patch details --- patches/react-native-onyx/details.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/react-native-onyx/details.md b/patches/react-native-onyx/details.md index 75e1d8488d1f..765902c6dd6c 100644 --- a/patches/react-native-onyx/details.md +++ b/patches/react-native-onyx/details.md @@ -6,6 +6,6 @@ > Reverts [Onyx PR #770 (the subscription-side skip for skippable collection member ids in subscribeToKey)](https://github.com/Expensify/react-native-onyx/pull/770) and the line [PR #779](https://github.com/Expensify/react-native-onyx/pull/779) added to work around [PR #770](https://github.com/Expensify/react-native-onyx/pull/770)'s silent-no-callback contract. -- Upstream PR/issue: TODO +- Upstream PR/issue: https://github.com/Expensify/react-native-onyx/pull/785 - E/App issue: https://github.com/Expensify/App/issues/86181 -- PR Introducing Patch: TODO \ No newline at end of file +- PR Introducing Patch: https://github.com/Expensify/App/pull/90764 \ No newline at end of file