Skip to content

Commit 4d41d2e

Browse files
authored
Merge pull request #85919 from callstack-internal/VickyStash/refactor/82871-bump-onyx-3.0.46
Onyx bump to v3.0.61
2 parents 5b50cdc + 37bb95f commit 4d41d2e

10 files changed

Lines changed: 75 additions & 59 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
"react-native-localize": "^3.5.4",
178178
"react-native-nitro-modules": "0.29.4",
179179
"react-native-nitro-sqlite": "9.2.0",
180-
"react-native-onyx": "3.0.60",
180+
"react-native-onyx": "3.0.61",
181181
"react-native-pager-view": "8.0.0",
182182
"react-native-pdf": "7.0.2",
183183
"react-native-permissions": "^5.4.0",

patches/react-native-onyx/details.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `react-native-onyx` patches
22

3-
### [react-native-onyx+3.0.60.patch](react-native-onyx+3.0.60.patch)
3+
### [react-native-onyx+3.0.61.patch](react-native-onyx+3.0.61.patch)
44

55
- 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.
66
- E/App issue: https://github.com/Expensify/App/issues/85416

patches/react-native-onyx/react-native-onyx+3.0.60.patch renamed to patches/react-native-onyx/react-native-onyx+3.0.61.patch

File renamed without changes.

src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {useFocusEffect} from '@react-navigation/native';
1+
import {useIsFocused} from '@react-navigation/native';
22
import {FlashList} from '@shopify/flash-list';
33
import type {FlashListRef, ListRenderItemInfo} from '@shopify/flash-list';
4-
import React, {useCallback, useDeferredValue, useEffect, useMemo, useRef, useState} from 'react';
4+
import React, {useCallback, useDeferredValue, useEffect, useEffectEvent, useMemo, useRef, useState} from 'react';
55
import {View} from 'react-native';
66
import type {ViewToken} from 'react-native';
77
import type {OnyxEntry} from 'react-native-onyx';
@@ -375,31 +375,39 @@ function MoneyRequestReportPreviewContent({
375375
carouselTransactionsRef.current = carouselTransactions;
376376
}, [carouselTransactions]);
377377

378-
useFocusEffect(
379-
useCallback(() => {
380-
const index = carouselTransactions.findIndex((transaction) => newTransactionIDs?.has(transaction.transactionID));
378+
const isFocused = useIsFocused();
379+
const getIsFocused = useEffectEvent(() => {
380+
return isFocused;
381+
});
382+
383+
useEffect(() => {
384+
const index = carouselTransactions.findIndex((transaction) => newTransactionIDs?.has(transaction.transactionID));
381385

382-
if (index < 0) {
386+
if (index < 0) {
387+
return;
388+
}
389+
const newTransaction = carouselTransactions.at(index);
390+
setTimeout(() => {
391+
if (!getIsFocused()) {
383392
return;
384393
}
385-
const newTransaction = carouselTransactions.at(index);
386-
setTimeout(() => {
387-
// If the new transaction is not available at the index it was on before the delay, avoid the scrolling
388-
// because we are scrolling to either a wrong or unavailable transaction (which can cause crash).
389-
if (newTransaction?.transactionID !== carouselTransactionsRef.current.at(index)?.transactionID) {
390-
return;
391-
}
392-
393-
carouselRef.current?.scrollToIndex({
394-
index,
395-
viewOffset: -2 * styles.gap2.gap,
396-
animated: true,
397-
});
398-
}, CONST.ANIMATED_TRANSITION);
399-
400-
// eslint-disable-next-line react-hooks/exhaustive-deps
401-
}, [newTransactionIDs]),
402-
);
394+
395+
// If the new transaction is not available at the index it was on before the delay, avoid the scrolling
396+
// because we are scrolling to either a wrong or unavailable transaction (which can cause crash).
397+
if (newTransaction?.transactionID !== carouselTransactionsRef.current.at(index)?.transactionID) {
398+
return;
399+
}
400+
401+
carouselRef.current?.scrollToIndex({
402+
index,
403+
viewOffset: -2 * styles.gap2.gap,
404+
animated: true,
405+
});
406+
}, CONST.ANIMATED_TRANSITION);
407+
408+
// We only want to scroll to a new transaction when the set of new transaction IDs changes.
409+
// eslint-disable-next-line react-hooks/exhaustive-deps
410+
}, [newTransactionIDs]);
403411

404412
const onViewableItemsChanged = useRef(({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => {
405413
const newIndex = viewableItems.at(0)?.index;

src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {useIsFocused} from '@react-navigation/native';
21
import type {ListRenderItem} from '@shopify/flash-list';
32
import React, {useCallback, useMemo, useRef, useState} from 'react';
43
import type {LayoutChangeEvent} from 'react-native';
@@ -123,9 +122,7 @@ function MoneyRequestReportPreview({
123122
selector: hasOnceLoadedReportActionsSelector,
124123
});
125124
const newTransactions = useNewTransactions(hasOnceLoadedReportActions, transactions);
126-
const isFocused = useIsFocused();
127-
// We only want to highlight the new expenses if the screen is focused.
128-
const newTransactionIDs = isFocused ? new Set(newTransactions.map((transaction) => transaction.transactionID)) : undefined;
125+
const newTransactionIDs = new Set(newTransactions.map((transaction) => transaction.transactionID));
129126

130127
const transactionPreviewContainerStyles = [styles.h100, reportPreviewStyles.transactionPreviewCarouselStyle];
131128

src/pages/workspace/withPolicy.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {ComponentType} from 'react';
2-
import React from 'react';
2+
import React, {useEffect} from 'react';
33
import type {OnyxEntry} from 'react-native-onyx';
44
import useOnyx from '@hooks/useOnyx';
55
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -94,9 +94,12 @@ export default function <TProps extends WithPolicyProps>(WrappedComponent: Compo
9494
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */
9595
const isLoadingPolicy = !hasLoadedApp || (!!policyID && isLoadingOnyxValue(policyResults, policyDraftResults));
9696

97-
if (policyID && policyID.length > 0) {
97+
useEffect(() => {
98+
if (!policyID) {
99+
return;
100+
}
98101
updateLastAccessedWorkspace(policyID);
99-
}
102+
}, [policyID]);
100103

101104
return (
102105
<WrappedComponent

tests/unit/OptionsListUtilsTest.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,7 +3513,7 @@ describe('OptionsListUtils', () => {
35133513
expect(canCreate).toBe(false);
35143514
});
35153515

3516-
it('createOptionList() localization', () => {
3516+
it('createOptionList() localization', async () => {
35173517
renderLocaleContextProvider();
35183518
// Given a set of reports and personal details
35193519
// When we call createOptionList and extract the reports
@@ -3522,18 +3522,15 @@ describe('OptionsListUtils', () => {
35223522
// Then the returned reports should match the expected values
35233523
expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`);
35243524

3525-
return (
3526-
waitForBatchedUpdates()
3527-
// When we set the preferred locale to Spanish
3528-
.then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES))
3529-
.then(() => {
3530-
// When we call createOptionList again
3531-
const newReports = createOptionList(PERSONAL_DETAILS, EMPTY_PRIVATE_IS_ARCHIVED_MAP, REPORTS, undefined).reports;
3532-
// Then the returned reports should change to Spanish
3533-
// cspell:disable-next-line
3534-
expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic');
3535-
})
3536-
);
3525+
await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES);
3526+
3527+
await waitForBatchedUpdates();
3528+
3529+
// When we call createOptionList again
3530+
const newReports = createOptionList(PERSONAL_DETAILS, EMPTY_PRIVATE_IS_ARCHIVED_MAP, REPORTS, undefined).reports;
3531+
// Then the returned reports should change to Spanish
3532+
// cspell:disable-next-line
3533+
expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic');
35373534
});
35383535
});
35393536

@@ -3607,6 +3604,8 @@ describe('OptionsListUtils', () => {
36073604
'1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT),
36083605
},
36093606
});
3607+
await waitForBatchedUpdates();
3608+
36103609
// When we call createOptionList with report 10 marked as archived
36113610
const archivedMap: PrivateIsArchivedMap = {
36123611
[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}10`]: !!reportNameValuePairs.private_isArchived,

tests/unit/ReportSecondaryActionUtilsTest.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,16 @@ describe('getSecondaryAction', () => {
5454
beforeAll(() => {
5555
Onyx.init({
5656
keys: ONYXKEYS,
57+
initialKeyStates: {
58+
[ONYXKEYS.SESSION]: SESSION,
59+
[ONYXKEYS.PERSONAL_DETAILS_LIST]: {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS, [APPROVER_ACCOUNT_ID]: {accountID: APPROVER_ACCOUNT_ID, login: APPROVER_EMAIL}},
60+
},
5761
});
5862
});
5963

6064
beforeEach(async () => {
6165
jest.clearAllMocks();
6266
Onyx.clear();
63-
await Onyx.merge(ONYXKEYS.SESSION, SESSION);
64-
await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS, [APPROVER_ACCOUNT_ID]: {accountID: APPROVER_ACCOUNT_ID, login: APPROVER_EMAIL}});
6567
});
6668

6769
it('should always return default options', () => {

tests/unit/SequentialQueueTest.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ describe('SequentialQueue', () => {
5353
};
5454
SequentialQueue.push(requestWithConflictResolution);
5555
expect(getLength()).toBe(1);
56-
// We know there is only one request in the queue, so we can get the first one and verify
57-
// that the persisted request is the second one.
58-
const persistedRequest = getAll().at(0);
59-
expect(persistedRequest?.data?.accountID).toBe(56789);
56+
// We know there is only one request and it's ongoing.
57+
// We can get it and verify that the ongoing request is the second one.
58+
const ongoingRequest = getOngoingRequest();
59+
expect(ongoingRequest?.data?.accountID).toBe(56789);
6060
});
6161

6262
it('should push two requests with conflict resolution and push', () => {
@@ -109,7 +109,9 @@ describe('SequentialQueue', () => {
109109
};
110110

111111
SequentialQueue.push(requestWithConflictResolution);
112-
expect(getLength()).toBe(2);
112+
113+
const ongoingRequest = getOngoingRequest();
114+
expect(ongoingRequest?.data?.accountID).toBe(56789);
113115
});
114116

115117
it('should replace request request in queue while a similar one is ongoing', async () => {
@@ -175,9 +177,14 @@ describe('SequentialQueue', () => {
175177

176178
expect(getLength()).toBe(4);
177179
const persistedRequests = getAll();
178-
// We know ReconnectApp is at index 1 in the queue, so we can get it to verify
180+
const ongoingRequest = getOngoingRequest();
181+
182+
// The first OpenReport call is ongoing
183+
expect(ongoingRequest?.command).toBe('OpenReport');
184+
185+
// We know ReconnectApp is at index 0 in the queue now, so we can get it to verify
179186
// that was replaced by the new request.
180-
expect(persistedRequests.at(1)?.data?.accountID).toBe(56789);
187+
expect(persistedRequests.at(0)?.data?.accountID).toBe(56789);
181188
});
182189

183190
// need to test a rance condition between processing the next request and then pushing a new request with conflict resolver

0 commit comments

Comments
 (0)