From 7ab40b1000b5a6b4fd50e0f5da224b7e949e9975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 4 May 2026 13:41:11 +0100 Subject: [PATCH 1/6] Use newest Onyx changes --- package-lock.json | 8 ++--- package.json | 2 +- patches/react-native-onyx/details.md | 7 ---- .../react-native-onyx+3.0.66.patch | 35 ------------------- 4 files changed, 5 insertions(+), 47 deletions(-) delete mode 100644 patches/react-native-onyx/details.md delete mode 100644 patches/react-native-onyx/react-native-onyx+3.0.66.patch diff --git a/package-lock.json b/package-lock.json index 7eed47aea402..57498065b9d9 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.66", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34592,9 +34592,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.66", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.66.tgz", - "integrity": "sha512-pNDHZsJeSCJPrV9gw4igSTWXx8dayIaTkCQrRaENEPlbDNdRCSP7LSEc2VvyyMwDXRFCL/7CmXZOwRnF6wsUfg==", + "version": "3.0.71", + "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", + "integrity": "sha512-a+2gg1kCIniOe4aqKG5FNRaeBMzxSYAZR9do4CqPfVQ78vbQhJS0wu15XgLiHG2V6sULbXAikxc/7n7EScOcBQ==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 7ceba3f8bece..fe75cfb8831d 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.66", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", "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 deleted file mode 100644 index 719206af5632..000000000000 --- a/patches/react-native-onyx/details.md +++ /dev/null @@ -1,7 +0,0 @@ -# `react-native-onyx` patches - -### [react-native-onyx+3.0.66.patch](react-native-onyx+3.0.66.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 diff --git a/patches/react-native-onyx/react-native-onyx+3.0.66.patch b/patches/react-native-onyx/react-native-onyx+3.0.66.patch deleted file mode 100644 index 7b321b0f51de..000000000000 --- a/patches/react-native-onyx/react-native-onyx+3.0.66.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({ From 638731d75886bb58b9b2f3097f0b760651df9384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 4 May 2026 13:41:14 +0100 Subject: [PATCH 2/6] Remove remaining initWithStoredValues from codebase --- src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts | 2 +- tests/unit/OnyxDerivedTest.tsx | 2 -- tests/unit/TryNewDotUtilsTest.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts b/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts index b9f0f24402d2..803d723aae4c 100644 --- a/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts +++ b/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts @@ -8,7 +8,7 @@ import usePrevious from './usePrevious'; * transition of `DOMAIN_MEMBERS_SELECTED_FOR_MOVE` from non-empty to empty. */ function useClearSelectedDomainMembersOnMoveComplete(clearSelectedMembers: () => void) { - const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, {initWithStoredValues: false}); + const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE); const prevSelectedMemberAccountIDs = usePrevious(selectedMemberAccountIDs); const selectedCount = selectedMemberAccountIDs?.length ?? 0; const previousSelectedCount = prevSelectedMemberAccountIDs?.length ?? 0; diff --git a/tests/unit/OnyxDerivedTest.tsx b/tests/unit/OnyxDerivedTest.tsx index 236bafe3b329..e04d5c7f2544 100644 --- a/tests/unit/OnyxDerivedTest.tsx +++ b/tests/unit/OnyxDerivedTest.tsx @@ -36,8 +36,6 @@ describe('OnyxDerived', () => { }); beforeEach(async () => { - // The reportAttributes locale connection uses initWithStoredValues: false, - // so it doesn't fire after Onyx.clear(). Setting this triggers recomputation. await Onyx.set(ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING, false); await waitForBatchedUpdates(); }); diff --git a/tests/unit/TryNewDotUtilsTest.ts b/tests/unit/TryNewDotUtilsTest.ts index a2f4244aed33..7104ff4a49c9 100644 --- a/tests/unit/TryNewDotUtilsTest.ts +++ b/tests/unit/TryNewDotUtilsTest.ts @@ -11,7 +11,6 @@ function getTryNewDot(): Promise { return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.NVP_TRY_NEW_DOT, - initWithStoredValues: true, callback: (value) => { Onyx.disconnect(connectionID); resolve(value ?? null); From 4425cee94aa0e1bfa5ec3263597cf4ebf51295f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 6 May 2026 12:10:58 +0100 Subject: [PATCH 3/6] Convert DOMAIN_MEMBERS_SELECTED_FOR_MOVE to RAM-only --- src/ONYXKEYS.ts | 4 ++-- .../useClearSelectedDomainMembersOnMoveComplete.ts | 4 ++-- src/libs/actions/Domain.ts | 4 ++-- .../domain/Members/MoveUsersBetweenGroupsPage.tsx | 12 ++++++++++-- src/setup/index.ts | 1 + tests/actions/DomainTest.ts | 8 ++++---- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 88eb462a12ac..3c56d98fd373 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -433,7 +433,7 @@ const ONYXKEYS = { MY_DOMAIN_SECURITY_GROUPS: 'myDomainSecurityGroups', /** Selected domain member account IDs for the move-to-group operation */ - DOMAIN_MEMBERS_SELECTED_FOR_MOVE: 'domainMembersSelectedForMove', + RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE: 'domainMembersSelectedForMove', // The theme setting set by the user in preferences. // This can be either "light", "dark", "system", "light-contrast", "dark-contrast" or "system-contrast" @@ -1463,7 +1463,7 @@ type OnyxValuesMapping = { [ONYXKEYS.IS_BETA]: boolean; [ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM]: boolean; [ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record; - [ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE]: string[]; + [ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE]: string[]; [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; diff --git a/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts b/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts index 803d723aae4c..f347932faff2 100644 --- a/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts +++ b/src/hooks/useClearSelectedDomainMembersOnMoveComplete.ts @@ -5,10 +5,10 @@ import usePrevious from './usePrevious'; /** * Clears local member selection after move flow completion by reacting to a - * transition of `DOMAIN_MEMBERS_SELECTED_FOR_MOVE` from non-empty to empty. + * transition of `RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE` from non-empty to empty. */ function useClearSelectedDomainMembersOnMoveComplete(clearSelectedMembers: () => void) { - const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE); + const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE); const prevSelectedMemberAccountIDs = usePrevious(selectedMemberAccountIDs); const selectedCount = selectedMemberAccountIDs?.length ?? 0; const previousSelectedCount = prevSelectedMemberAccountIDs?.length ?? 0; diff --git a/src/libs/actions/Domain.ts b/src/libs/actions/Domain.ts index 38606fadf550..5d3d558b3ed8 100644 --- a/src/libs/actions/Domain.ts +++ b/src/libs/actions/Domain.ts @@ -1834,11 +1834,11 @@ function clearChangeDomainSecurityGroupError(domainAccountID: number, memberEmai } function setDomainMembersSelectedForMove(memberAccountIDs: string[]) { - Onyx.set(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, memberAccountIDs); + Onyx.set(ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, memberAccountIDs); } function clearDomainMembersSelectedForMove() { - Onyx.set(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, []); + Onyx.set(ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, []); } /** diff --git a/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx b/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx index b463e29f8e6e..02def7f8be2f 100644 --- a/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx +++ b/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx @@ -1,5 +1,5 @@ import {domainNameSelector, groupsSelector} from '@selectors/Domain'; -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -36,10 +36,18 @@ function MoveUsersBetweenGroupsPage({route}: MoveUsersBetweenGroupsPageProps) { const [selectedGroupId, setSelectedGroupId] = useState(); const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector}); const [securityGroups] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: groupsSelector}); - const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE); + const [selectedMemberAccountIDs] = useOnyx(ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE); const memberCount = selectedMemberAccountIDs?.length ?? 0; + // Redirect back to the members page if there are no members to move. + useEffect(() => { + if (memberCount > 0) { + return; + } + Navigation.navigate(ROUTES.DOMAIN_MEMBERS.getRoute(domainAccountID)); + }, [memberCount, domainAccountID]); + const data: SecurityGroupItem[] = (securityGroups ?? []).map(({id, details}) => ({ text: details.name ?? '', keyForList: id, diff --git a/src/setup/index.ts b/src/setup/index.ts index 844b85eee059..62438c38a5e0 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -70,6 +70,7 @@ export default function () { ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE, ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD, + ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, ], }); diff --git a/tests/actions/DomainTest.ts b/tests/actions/DomainTest.ts index e81a31900a9a..a9d12b9f2bbd 100644 --- a/tests/actions/DomainTest.ts +++ b/tests/actions/DomainTest.ts @@ -980,7 +980,7 @@ describe('actions/Domain', () => { const memberAccountIDs = ['100', '200', '300']; setDomainMembersSelectedForMove(memberAccountIDs); await TestHelper.getOnyxData({ - key: ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, + key: ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, waitForCollectionCallback: false, callback: (value) => { expect(value).toEqual(memberAccountIDs); @@ -991,7 +991,7 @@ describe('actions/Domain', () => { it('sets an empty array when called with empty array', async () => { setDomainMembersSelectedForMove([]); await TestHelper.getOnyxData({ - key: ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, + key: ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, waitForCollectionCallback: false, callback: (value) => { expect(value).toEqual([]); @@ -1000,10 +1000,10 @@ describe('actions/Domain', () => { }); it('clearDomainMembersSelectedForMove resets the selection to an empty array', async () => { - await Onyx.set(ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, ['100', '200']); + await Onyx.set(ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, ['100', '200']); clearDomainMembersSelectedForMove(); await TestHelper.getOnyxData({ - key: ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE, + key: ONYXKEYS.RAM_ONLY_DOMAIN_MEMBERS_SELECTED_FOR_MOVE, waitForCollectionCallback: false, callback: (value) => { expect(value).toEqual([]); From 67dc50dc1d54bc7c37f1a919ffc36ba7ae21393d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 7 May 2026 09:17:42 +0100 Subject: [PATCH 4/6] Use newest Onyx changes --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 994478bc0288..0475808c8c2f 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": "git+https://github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d83e8b75700f0c81fde0621ada17612753e37ad2", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34593,8 +34593,8 @@ }, "node_modules/react-native-onyx": { "version": "3.0.71", - "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", - "integrity": "sha512-a+2gg1kCIniOe4aqKG5FNRaeBMzxSYAZR9do4CqPfVQ78vbQhJS0wu15XgLiHG2V6sULbXAikxc/7n7EScOcBQ==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#d83e8b75700f0c81fde0621ada17612753e37ad2", + "integrity": "sha512-7l0S39J5G7Ek6lRh7W2Phv3FPP+z84q8VXEJ1ZeYt9WNw+3oBGPAmLzH873BIFHBV1I6aigToXNY5cL+OF/h7g==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index cef770ceb0dd..8ebbc64c75b8 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": "git+https://github.com/Expensify/react-native-onyx.git#a4d5632a5608560cc3b8405255431e539c162267", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d83e8b75700f0c81fde0621ada17612753e37ad2", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", From 4dc3732fc10f132316307b489ef14c1d85da6cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 25 May 2026 08:07:15 +0100 Subject: [PATCH 5/6] Bump Onyx to 3.0.75 --- package-lock.json | 8 +-- package.json | 2 +- patches/react-native-onyx/details.md | 11 --- .../react-native-onyx+3.0.73.patch | 67 ------------------- 4 files changed, 5 insertions(+), 83 deletions(-) delete mode 100644 patches/react-native-onyx/details.md delete mode 100644 patches/react-native-onyx/react-native-onyx+3.0.73.patch diff --git a/package-lock.json b/package-lock.json index 05393b27dfe8..91500d81664e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,7 +116,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.73", + "react-native-onyx": "3.0.75", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34996,9 +34996,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.73", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.73.tgz", - "integrity": "sha512-0fvL8q7Rx3QBoHJJLB4e2wd0a9/4f//FvTBGKJliU5rkO/05APKZKFxMprSQpBm+O+i3T2R1lwSQbeaj9AbIDA==", + "version": "3.0.75", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.75.tgz", + "integrity": "sha512-HcfN0/uhBjEH7GRrWJtslYb3KlxFSSnIlNcYm0EZsWvdiHQFj9pBgNrpLVRUUMCkmU02aRsu/ZXwuDjnjZ+d6Q==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 54b2c30967d1..eecbb5345c7e 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,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.73", + "react-native-onyx": "3.0.75", "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 deleted file mode 100644 index 8d435bf932d1..000000000000 --- a/patches/react-native-onyx/details.md +++ /dev/null @@ -1,11 +0,0 @@ -# `react-native-onyx` patches - -### [react-native-onyx+3.0.73.patch](react-native-onyx+3.0.73.patch) - -- 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: https://github.com/Expensify/react-native-onyx/pull/785 -- E/App issue: https://github.com/Expensify/App/issues/86181 -- PR Introducing Patch: https://github.com/Expensify/App/pull/90764 \ No newline at end of file diff --git a/patches/react-native-onyx/react-native-onyx+3.0.73.patch b/patches/react-native-onyx/react-native-onyx+3.0.73.patch deleted file mode 100644 index 81d048105d69..000000000000 --- a/patches/react-native-onyx/react-native-onyx+3.0.73.patch +++ /dev/null @@ -1,67 +0,0 @@ -diff --git a/node_modules/react-native-onyx/dist/OnyxUtils.js b/node_modules/react-native-onyx/dist/OnyxUtils.js -index 87d163f..1971cd2 100644 ---- a/node_modules/react-native-onyx/dist/OnyxUtils.js -+++ b/node_modules/react-native-onyx/dist/OnyxUtils.js -@@ -886,24 +886,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. -@@ -1440,12 +1422,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. - */ -@@ -1504,6 +1480,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 8a0bd987c6887fe5555228246f903881f706c4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 28 May 2026 13:49:00 +0100 Subject: [PATCH 6/6] Exclude screen from LAST_VISITED_PATH --- src/CONST/index.ts | 1 + src/libs/actions/App.ts | 8 ++++++++ src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index db9567f28314..695dbdf85a1d 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8567,6 +8567,7 @@ const CONST = { SCREENS.VALIDATE_LOGIN, SCREENS.MIGRATED_USER_WELCOME_MODAL.ROOT, SCREENS.MONEY_REQUEST.STEP_SCAN, + SCREENS.DOMAIN.MEMBERS_MOVE_TO_GROUP, ...Object.values(SCREENS.MULTIFACTOR_AUTHENTICATION), ] as string[], diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 098ebb3dd54e..a14e9aecdbcd 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -1,4 +1,5 @@ // Issue - https://github.com/Expensify/App/issues/26719 +import {findFocusedRoute} from '@react-navigation/native'; import {Str} from 'expensify-common'; import type {AppStateStatus} from 'react-native'; import {AppState} from 'react-native'; @@ -244,6 +245,13 @@ function saveCurrentPathBeforeBackground() { return; } + // Honor the same exclusion list as parseAndLogRoute in NavigationRoot, so screens that + // shouldn't be restored on relaunch (transient/RAM-dependent routes) aren't persisted here either. + const focusedRoute = findFocusedRoute(currentState); + if (focusedRoute && CONST.EXCLUDE_FROM_LAST_VISITED_PATH.includes(focusedRoute.name)) { + return; + } + const currentPath = getPathFromState(currentState); if (currentPath) { diff --git a/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx b/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx index 28aa7a4de3eb..ec11dfd1aa18 100644 --- a/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx +++ b/src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx @@ -41,7 +41,10 @@ function MoveUsersBetweenGroupsPage({route}: MoveUsersBetweenGroupsPageProps) { const memberCount = selectedMemberAccountIDs?.length ?? 0; - // Redirect back to the members page if there are no members to move. + // Redirect back to the members page when there's no selection (e.g. a web URL refresh or a + // shared deep link landing here without going through the members page first). Native cold + // restarts are handled by excluding this screen from LAST_VISITED_PATH so the user is restored + // to the underlying members page directly. useEffect(() => { if (memberCount > 0) { return;