From 033019d1700c9a65c3de438876308c6a8a28c7f6 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 20 Mar 2026 10:15:03 +0100 Subject: [PATCH 01/10] Revert "Merge pull request #85767 from margelo/@chrispader/dowgrade-onyx-to-v3.0.45" This reverts commit 5277b58aa75817c9959381de7cd01be0260712c0, reversing changes made to 79334708ddbd70cacb700ecaffc03f274e80b9d6. --- package-lock.json | 8 +-- package.json | 2 +- .../MoneyRequestReportPreviewContent.tsx | 51 +++++++++++-------- .../MoneyRequestReportPreview/index.tsx | 4 +- src/pages/workspace/withPolicy.tsx | 9 ++-- tests/actions/ReportTest.ts | 8 +++ tests/actions/SessionTest.ts | 4 ++ tests/unit/APITest.ts | 4 +- tests/unit/OptionsListUtilsTest.tsx | 25 +++++---- tests/unit/ReportSecondaryActionUtilsTest.ts | 6 ++- tests/unit/SequentialQueueTest.ts | 21 +++++--- 11 files changed, 86 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9eb8b2b56cca..1a284c5bcfa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,7 +117,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.45", + "react-native-onyx": "3.0.46", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34861,9 +34861,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.45", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.45.tgz", - "integrity": "sha512-oJyizoazptOzKfY8ENDu0Y+QcFvCvRFySIEgQGOMZ0SDvmuni9MJ57zPd6/C/3n84GzTCJ5yDLmOFQRiHuPoRQ==", + "version": "3.0.46", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.46.tgz", + "integrity": "sha512-/dB5PrK+AZ4QiaFdIdb2iC1q5tu7L3n6pxh+QZJiMhDORlQT6jK832b/6sqwgQ3qIZCrrsnqmyLVKCqEbYTrlQ==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 8309042741ea..74aedafe0a89 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.45", + "react-native-onyx": "3.0.46", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index a1536c827050..10d28496053f 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -1,4 +1,4 @@ -import {useFocusEffect} from '@react-navigation/native'; +import {useIsFocused} from '@react-navigation/native'; import {hasSeenTourSelector} from '@selectors/Onboarding'; import {FlashList} from '@shopify/flash-list'; import type {FlashListRef, ListRenderItemInfo} from '@shopify/flash-list'; @@ -552,31 +552,40 @@ function MoneyRequestReportPreviewContent({ carouselTransactionsRef.current = carouselTransactions; }, [carouselTransactions]); - useFocusEffect( - useCallback(() => { - const index = carouselTransactions.findIndex((transaction) => newTransactionIDs?.has(transaction.transactionID)); + const isFocused = useIsFocused(); + const isFocusedRef = useRef(isFocused); - if (index < 0) { + useEffect(() => { + isFocusedRef.current = isFocused; + }, [isFocused]); + + useEffect(() => { + const index = carouselTransactions.findIndex((transaction) => newTransactionIDs?.has(transaction.transactionID)); + + if (index < 0) { + return; + } + const newTransaction = carouselTransactions.at(index); + setTimeout(() => { + if (!isFocusedRef.current) { + return; + } + // If the new transaction is not available at the index it was on before the delay, avoid the scrolling + // because we are scrolling to either a wrong or unavailable transaction (which can cause crash). + if (newTransaction?.transactionID !== carouselTransactionsRef.current.at(index)?.transactionID) { return; } - const newTransaction = carouselTransactions.at(index); - setTimeout(() => { - // If the new transaction is not available at the index it was on before the delay, avoid the scrolling - // because we are scrolling to either a wrong or unavailable transaction (which can cause crash). - if (newTransaction?.transactionID !== carouselTransactionsRef.current.at(index)?.transactionID) { - return; - } - carouselRef.current?.scrollToIndex({ - index, - viewOffset: -2 * styles.gap2.gap, - animated: true, - }); - }, CONST.ANIMATED_TRANSITION); + carouselRef.current?.scrollToIndex({ + index, + viewOffset: -2 * styles.gap2.gap, + animated: true, + }); + }, CONST.ANIMATED_TRANSITION); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [newTransactionIDs]), - ); + // We only want to scroll to a new transaction when the set of new transaction IDs changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [newTransactionIDs]); const onViewableItemsChanged = useRef(({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => { const newIndex = viewableItems.at(0)?.index; diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index 9ce325e9dc7b..08e953542f52 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -1,4 +1,3 @@ -import {useIsFocused} from '@react-navigation/native'; import type {ListRenderItem} from '@shopify/flash-list'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent} from 'react-native'; @@ -122,9 +121,8 @@ function MoneyRequestReportPreview({ selector: hasOnceLoadedReportActionsSelector, }); const newTransactions = useNewTransactions(hasOnceLoadedReportActions, transactions); - const isFocused = useIsFocused(); // We only want to highlight the new expenses if the screen is focused. - const newTransactionIDs = isFocused ? new Set(newTransactions.map((transaction) => transaction.transactionID)) : undefined; + const newTransactionIDs = new Set(newTransactions.map((transaction) => transaction.transactionID)); const transactionPreviewContainerStyles = [styles.h100, reportPreviewStyles.transactionPreviewCarouselStyle]; diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index f4ef4b7212a5..751570ec2fde 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -1,5 +1,5 @@ import type {ComponentType} from 'react'; -import React from 'react'; +import React, {useEffect} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useOnyx from '@hooks/useOnyx'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -90,9 +90,12 @@ export default function (WrappedComponent: Compo /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */ const isLoadingPolicy = !hasLoadedApp || (!!policyID && isLoadingOnyxValue(policyResults, policyDraftResults)); - if (policyID && policyID.length > 0) { + useEffect(() => { + if (!policyID) { + return; + } updateLastAccessedWorkspace(policyID); - } + }, [policyID]); return ( { currentUserAccountID: TEST_USER_ACCOUNT_ID, }); + await waitForBatchedUpdates(); + // Need the reportActionID to delete the comments const newComment = PersistedRequests.getAll().at(1); const reportActionID = newComment?.data?.reportActionID as string | undefined; @@ -2039,6 +2041,9 @@ describe('actions/Report', () => { const newComment = PersistedRequests.getAll().at(0); const reportActionID = newComment?.data?.reportActionID as string | undefined; const reportAction = TestHelper.buildTestReportComment(created, TEST_USER_ACCOUNT_ID, reportActionID); + + await waitForBatchedUpdates(); + await Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); // wait for Onyx.connect execute the callback and start processing the queue @@ -2232,6 +2237,7 @@ describe('actions/Report', () => { expect(requests?.at(0)?.data?.reportComment).toBe('value3'); await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + await waitForBatchedUpdates(); TestHelper.expectAPICommandToHaveBeenCalled(WRITE_COMMANDS.UPDATE_COMMENT, 1); }); @@ -2330,6 +2336,8 @@ describe('actions/Report', () => { expect(requests?.at(0)?.data?.reportComment).toBe('value3'); await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + await waitForBatchedUpdates(); + TestHelper.expectAPICommandToHaveBeenCalled(WRITE_COMMANDS.UPDATE_COMMENT, 1); }); diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index 94908d2eef7a..13b52bf0f4ee 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -190,6 +190,8 @@ describe('Session', () => { await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + await waitForBatchedUpdates(); + expect(getAllPersistedRequests().length).toBe(0); }); @@ -227,6 +229,8 @@ describe('Session', () => { await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + await waitForBatchedUpdates(); + expect(getAllPersistedRequests().length).toBe(0); }); diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 8e81e4bfd42a..f5c144464ac7 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -436,7 +436,7 @@ describe('APITests', () => { }); Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); - expect(NetworkStore.isOffline()).toBe(false); + expect(NetworkStore.isOffline()).toBe(true); expect(NetworkStore.isAuthenticating()).toBe(false); return waitForBatchedUpdates(); }) @@ -551,7 +551,7 @@ describe('APITests', () => { API.write('MockCommandThree' as WriteCommand, {}); // THEN the retryable requests should immediately be added to the persisted requests - expect(PersistedRequests.getAll().length).toBe(2); + expect(PersistedRequests.getLength()).toBe(2); // WHEN we wait for the queue to run and finish processing return waitForBatchedUpdates(); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 0e09921d573d..2ba7b1d614bd 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -3273,7 +3273,7 @@ describe('OptionsListUtils', () => { expect(canCreate).toBe(false); }); - it('createOptionList() localization', () => { + it('createOptionList() localization', async () => { renderLocaleContextProvider(); // Given a set of reports and personal details // When we call createOptionList and extract the reports @@ -3282,18 +3282,15 @@ describe('OptionsListUtils', () => { // Then the returned reports should match the expected values expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`); - return ( - waitForBatchedUpdates() - // When we set the preferred locale to Spanish - .then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES)) - .then(() => { - // When we call createOptionList again - const newReports = createOptionList(PERSONAL_DETAILS, CURRENT_USER_ACCOUNT_ID, EMPTY_PRIVATE_IS_ARCHIVED_MAP, REPORTS).reports; - // Then the returned reports should change to Spanish - // cspell:disable-next-line - expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); - }) - ); + await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES); + + await waitForBatchedUpdates(); + + // When we call createOptionList again + const newReports = createOptionList(PERSONAL_DETAILS, CURRENT_USER_ACCOUNT_ID, EMPTY_PRIVATE_IS_ARCHIVED_MAP, REPORTS).reports; + // Then the returned reports should change to Spanish + // cspell:disable-next-line + expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); }); }); @@ -3367,6 +3364,8 @@ describe('OptionsListUtils', () => { '1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT), }, }); + await waitForBatchedUpdates(); + // When we call createOptionList with report 10 marked as archived const archivedMap: PrivateIsArchivedMap = { [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}10`]: reportNameValuePairs.private_isArchived, diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index ba8da26a36e6..e925eeba8840 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -52,14 +52,16 @@ describe('getSecondaryAction', () => { beforeAll(() => { Onyx.init({ keys: ONYXKEYS, + initialKeyStates: { + [ONYXKEYS.SESSION]: SESSION, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS, [APPROVER_ACCOUNT_ID]: {accountID: APPROVER_ACCOUNT_ID, login: APPROVER_EMAIL}}, + }, }); }); beforeEach(async () => { jest.clearAllMocks(); Onyx.clear(); - await Onyx.merge(ONYXKEYS.SESSION, SESSION); - await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS, [APPROVER_ACCOUNT_ID]: {accountID: APPROVER_ACCOUNT_ID, login: APPROVER_EMAIL}}); }); it('should always return default options', () => { diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 805fdb8f3412..3ea245876b82 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -53,10 +53,10 @@ describe('SequentialQueue', () => { }; SequentialQueue.push(requestWithConflictResolution); expect(getLength()).toBe(1); - // We know there is only one request in the queue, so we can get the first one and verify - // that the persisted request is the second one. - const persistedRequest = getAll().at(0); - expect(persistedRequest?.data?.accountID).toBe(56789); + // We know there is only one request and it's ongoing. + // We can get it and verify that the ongoing request is the second one. + const ongoingRequest = getOngoingRequest(); + expect(ongoingRequest?.data?.accountID).toBe(56789); }); it('should push two requests with conflict resolution and push', () => { @@ -109,7 +109,9 @@ describe('SequentialQueue', () => { }; SequentialQueue.push(requestWithConflictResolution); - expect(getLength()).toBe(2); + + const ongoingRequest = getOngoingRequest(); + expect(ongoingRequest?.data?.accountID).toBe(56789); }); it('should replace request request in queue while a similar one is ongoing', async () => { @@ -175,9 +177,14 @@ describe('SequentialQueue', () => { expect(getLength()).toBe(4); const persistedRequests = getAll(); - // We know ReconnectApp is at index 1 in the queue, so we can get it to verify + const ongoingRequest = getOngoingRequest(); + + // The first OpenReport call is ongoing + expect(ongoingRequest?.command).toBe('OpenReport'); + + // We know ReconnectApp is at index 0 in the queue now, so we can get it to verify // that was replaced by the new request. - expect(persistedRequests.at(1)?.data?.accountID).toBe(56789); + expect(persistedRequests.at(0)?.data?.accountID).toBe(56789); }); // need to test a rance condition between processing the next request and then pushing a new request with conflict resolver From d20ea68de36829b9b977f6935016c6ff34b7da81 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 20 Mar 2026 11:50:57 +0100 Subject: [PATCH 02/10] Re-run checks From df0c9df060d1228bab1b08badd3f767c1fdf24ad Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 20 Mar 2026 16:06:46 +0100 Subject: [PATCH 03/10] Remove outdated comment --- .../ReportActionItem/MoneyRequestReportPreview/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index 08e953542f52..031a36890481 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -121,7 +121,6 @@ function MoneyRequestReportPreview({ selector: hasOnceLoadedReportActionsSelector, }); const newTransactions = useNewTransactions(hasOnceLoadedReportActions, transactions); - // We only want to highlight the new expenses if the screen is focused. const newTransactionIDs = new Set(newTransactions.map((transaction) => transaction.transactionID)); const transactionPreviewContainerStyles = [styles.h100, reportPreviewStyles.transactionPreviewCarouselStyle]; From 141c5f53c7130a49b00abe82bd408db45f80508f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 23 Mar 2026 16:10:47 +0100 Subject: [PATCH 04/10] Test onyx PR --- 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 dc307daddf8c..fe04c0616efb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,7 +117,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.50", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#a1638ea1800a795057abd395938a42df81f7ab5f", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34862,8 +34862,8 @@ }, "node_modules/react-native-onyx": { "version": "3.0.50", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.50.tgz", - "integrity": "sha512-ILYKm0/4s5KChKCjbo5xY2arbdkzsCosHcPcGbdCfLNa7nDN2/MxlOA/1+WjnUqyIcFW2n6jgxReCpMaIkAS+Q==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#a1638ea1800a795057abd395938a42df81f7ab5f", + "integrity": "sha512-K0YQ/sWQOiSIeTD6QeniwSv+Y98SKyJaf+pl/frS11KJv1zAiKvzLI5d9n2biXrYmXg6BmHHnYqV516aZ6xjBQ==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index cf422915a53b..8edf9dce426a 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.50", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#a1638ea1800a795057abd395938a42df81f7ab5f", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", From 883bbba70cd8c603f5de41466d13eca318ce83ff Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 24 Mar 2026 09:29:27 +0100 Subject: [PATCH 05/10] Test onyx PR --- 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 fe04c0616efb..4553e252bb82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,7 +117,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#a1638ea1800a795057abd395938a42df81f7ab5f", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#f63331e99fc0da31579f7efa2347e2d5ee0264d2", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34862,8 +34862,8 @@ }, "node_modules/react-native-onyx": { "version": "3.0.50", - "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#a1638ea1800a795057abd395938a42df81f7ab5f", - "integrity": "sha512-K0YQ/sWQOiSIeTD6QeniwSv+Y98SKyJaf+pl/frS11KJv1zAiKvzLI5d9n2biXrYmXg6BmHHnYqV516aZ6xjBQ==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#f63331e99fc0da31579f7efa2347e2d5ee0264d2", + "integrity": "sha512-KOSk1JcnSWf1dW8CersmreTM9n+H1qn5N8BLlg1UvvXXUQB0oUJPZPAYkRboaGQibUoK6HTtfC1Syui1e7hStA==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 8edf9dce426a..7393c1922059 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#a1638ea1800a795057abd395938a42df81f7ab5f", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#f63331e99fc0da31579f7efa2347e2d5ee0264d2", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", From 55f88ce1f52d6322e5fdfe5a0be57581a58eace7 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 30 Mar 2026 09:19:52 +0200 Subject: [PATCH 06/10] Test onyx updates --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf852ee356f7..ae04289d1e5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#9731da30d818cffc5352a82e4d3662ba5890ed9d", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#0aad1f887923470b587c38b71ab4dc896311afe8", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34816,7 +34816,7 @@ }, "node_modules/react-native-onyx": { "version": "3.0.54", - "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#9731da30d818cffc5352a82e4d3662ba5890ed9d", + "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#0aad1f887923470b587c38b71ab4dc896311afe8", "integrity": "sha512-8FVXaQ+GmRw+FLJvF41q2m63GK2qw0dRL+RDAtn63QJZPlUvSWVDalVy/kP5TG6LNet+kTajnhwiu2aOAfvQkg==", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 05218b82f135..00539972357d 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#9731da30d818cffc5352a82e4d3662ba5890ed9d", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#0aad1f887923470b587c38b71ab4dc896311afe8", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", From 6083a9c1d9b849fb444ed4b725c66916806c26b6 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 16 Apr 2026 09:15:20 +0200 Subject: [PATCH 07/10] Bump Onyx to 3.0.61 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa442166ddd2..03e545b26506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,7 +114,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.60", + "react-native-onyx": "3.0.61", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34702,9 +34702,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.60", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.60.tgz", - "integrity": "sha512-rW5pTGYcnpQv3vNv9IvBpvIFg3McY3f4jDe96eQbkxeRyzm592J8dfZAKjhavkoJkRo0gLP+oInxRvVOQihfYw==", + "version": "3.0.61", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.61.tgz", + "integrity": "sha512-heC6wja1BjarvV5o0V3DxZzu8ZU4owIrkPnh8tSYmg1TT2edXqNMnAtfVbC1WjXkkYOcKBmQbAaudNvyn9Ro7Q==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 1cc7827a4bca..9cd6ac848a56 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.29.4", "react-native-nitro-sqlite": "9.2.0", - "react-native-onyx": "3.0.60", + "react-native-onyx": "3.0.61", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", From 4daa23d28ac38c60d30e79e2ff8beb67dbfb90e4 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 16 Apr 2026 09:41:07 +0200 Subject: [PATCH 08/10] Update patch to v3.0.61 --- patches/react-native-onyx/details.md | 2 +- ...-native-onyx+3.0.60.patch => react-native-onyx+3.0.61.patch} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename patches/react-native-onyx/{react-native-onyx+3.0.60.patch => react-native-onyx+3.0.61.patch} (100%) diff --git a/patches/react-native-onyx/details.md b/patches/react-native-onyx/details.md index 29ad7ca30d77..6bb9930887b9 100644 --- a/patches/react-native-onyx/details.md +++ b/patches/react-native-onyx/details.md @@ -1,6 +1,6 @@ # `react-native-onyx` patches -### [react-native-onyx+3.0.60.patch](react-native-onyx+3.0.60.patch) +### [react-native-onyx+3.0.61.patch](react-native-onyx+3.0.61.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 diff --git a/patches/react-native-onyx/react-native-onyx+3.0.60.patch b/patches/react-native-onyx/react-native-onyx+3.0.61.patch similarity index 100% rename from patches/react-native-onyx/react-native-onyx+3.0.60.patch rename to patches/react-native-onyx/react-native-onyx+3.0.61.patch From 3d33800edc884db6e3186a19f024009421d95e8f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 17 Apr 2026 08:29:58 +0200 Subject: [PATCH 09/10] Remove extra waitForBatchedUpdates --- tests/actions/SessionTest.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index b2f9f1f53fd8..cd964559c749 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -200,8 +200,6 @@ describe('Session', () => { setHasRadio(true); await waitForBatchedUpdates(); - await waitForBatchedUpdates(); - expect(getAllPersistedRequests().length).toBe(0); }); @@ -243,8 +241,6 @@ describe('Session', () => { setHasRadio(true); await waitForBatchedUpdates(); - await waitForBatchedUpdates(); - expect(getAllPersistedRequests().length).toBe(0); }); From 37bb95f261efc22b492f17d4bb3a772d122ba3cd Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 20 Apr 2026 09:35:21 +0200 Subject: [PATCH 10/10] Simplify the code --- .../MoneyRequestReportPreviewContent.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index f140b7c021cb..74a4ff1d886a 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -1,7 +1,7 @@ import {useIsFocused} from '@react-navigation/native'; import {FlashList} from '@shopify/flash-list'; import type {FlashListRef, ListRenderItemInfo} from '@shopify/flash-list'; -import React, {useCallback, useDeferredValue, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useDeferredValue, useEffect, useEffectEvent, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {ViewToken} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -376,11 +376,9 @@ function MoneyRequestReportPreviewContent({ }, [carouselTransactions]); const isFocused = useIsFocused(); - const isFocusedRef = useRef(isFocused); - - useEffect(() => { - isFocusedRef.current = isFocused; - }, [isFocused]); + const getIsFocused = useEffectEvent(() => { + return isFocused; + }); useEffect(() => { const index = carouselTransactions.findIndex((transaction) => newTransactionIDs?.has(transaction.transactionID)); @@ -390,9 +388,10 @@ function MoneyRequestReportPreviewContent({ } const newTransaction = carouselTransactions.at(index); setTimeout(() => { - if (!isFocusedRef.current) { + if (!getIsFocused()) { return; } + // If the new transaction is not available at the index it was on before the delay, avoid the scrolling // because we are scrolling to either a wrong or unavailable transaction (which can cause crash). if (newTransaction?.transactionID !== carouselTransactionsRef.current.at(index)?.transactionID) {