Skip to content

Commit 0ace261

Browse files
authored
Merge pull request Expensify#88940 from callstack-internal/perf/upcoming-travel-perf11
[No QA] perf: fix PERF-11 in useUpcomingTravelReservations
2 parents 523b54b + 0bff546 commit 0ace261

3 files changed

Lines changed: 119 additions & 17 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import useOnyx from '@hooks/useOnyx';
2+
import {isTripRoom} from '@libs/ReportUtils';
3+
import ONYXKEYS from '@src/ONYXKEYS';
4+
import type {Report} from '@src/types/onyx';
5+
6+
function useTripRoomReports(): Report[] {
7+
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
8+
9+
return Object.values(reports ?? {}).filter((report): report is Report => !!report && isTripRoom(report));
10+
}
11+
12+
export default useTripRoomReports;

src/pages/home/UpcomingTravelSection/useUpcomingTravelReservations.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,30 @@
11
import {accountIDSelector} from '@selectors/Session';
22
import {useMemo} from 'react';
3-
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
43
import useOnyx from '@hooks/useOnyx';
5-
import {isTripRoom} from '@libs/ReportUtils';
64
import type {ReservationData} from '@libs/TripReservationUtils';
75
import {getReservationsFromTripReport} from '@libs/TripReservationUtils';
86
import CONST from '@src/CONST';
97
import ONYXKEYS from '@src/ONYXKEYS';
10-
import type {Report} from '@src/types/onyx';
11-
import mapOnyxCollectionItems from '@src/utils/mapOnyxCollectionItems';
8+
import useTripRoomReports from './useTripRoomReports';
129

1310
type UpcomingReservation = ReservationData & {
1411
reportID: string;
1512
};
1613

17-
const tripRoomSelector = (report: OnyxEntry<Report>): Report | undefined => {
18-
if (!report || !isTripRoom(report)) {
19-
return;
20-
}
21-
return report;
22-
};
23-
24-
const allTripRoomsSelector = (reports: OnyxCollection<Report>) => mapOnyxCollectionItems(reports, tripRoomSelector);
25-
2614
function useUpcomingTravelReservations(): UpcomingReservation[] {
27-
const [tripRoomReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: allTripRoomsSelector});
15+
const tripRoomReports = useTripRoomReports();
2816
const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: accountIDSelector});
2917

3018
return useMemo(() => {
3119
const now = new Date();
3220
const windowEnd = new Date(now);
3321
windowEnd.setDate(windowEnd.getDate() + CONST.UPCOMING_TRAVEL_WINDOW_DAYS);
3422

35-
const reports = Object.values(tripRoomReports ?? {});
3623
const upcoming: UpcomingReservation[] = [];
3724

38-
for (const report of reports) {
25+
for (const report of tripRoomReports) {
3926
// Only include reservations where the current user is the traveler
40-
if (!report || report.ownerAccountID !== accountID) {
27+
if (report.ownerAccountID !== accountID) {
4128
continue;
4229
}
4330
const reservations = getReservationsFromTripReport(report);

tests/unit/hooks/useUpcomingTravelReservations.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,4 +656,107 @@ describe('useUpcomingTravelReservations', () => {
656656
expect(result.current).toEqual([]);
657657
});
658658
});
659+
660+
it('should skip reservations with invalid start date and keep valid ones', async () => {
661+
const invalidFlight = makeAirPnr('PNR_INVALID', 'not-a-date', 'not-a-date');
662+
const validFlight = makeAirPnr('PNR_VALID', daysFromNow(2), daysFromNow(2, 15));
663+
const tripRoom = makeTripRoomReport('1000', [invalidFlight, validFlight]);
664+
665+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1000`, tripRoom);
666+
await waitForBatchedUpdates();
667+
668+
const {result} = renderHook(() => useUpcomingTravelReservations());
669+
670+
await waitFor(() => {
671+
expect(result.current).toHaveLength(1);
672+
});
673+
expect(result.current.at(0)?.reservation.reservationID).toBe('PNR_VALID');
674+
});
675+
676+
it('should return empty array when all reservations have invalid start dates', async () => {
677+
const invalidFlight = makeAirPnr('PNR_INVALID_ALL', '', '');
678+
const tripRoom = makeTripRoomReport('1001', [invalidFlight]);
679+
680+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1001`, tripRoom);
681+
await waitForBatchedUpdates();
682+
683+
const {result} = renderHook(() => useUpcomingTravelReservations());
684+
685+
await waitFor(() => {
686+
expect(result.current).toEqual([]);
687+
});
688+
});
689+
690+
it('should include reservation at the exact 7-day boundary', async () => {
691+
const boundaryFlight = makeAirPnr('PNR_BOUNDARY', daysFromNow(CONST.UPCOMING_TRAVEL_WINDOW_DAYS, 0), daysFromNow(CONST.UPCOMING_TRAVEL_WINDOW_DAYS, 3));
692+
const tripRoom = makeTripRoomReport('1002', [boundaryFlight]);
693+
694+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1002`, tripRoom);
695+
await waitForBatchedUpdates();
696+
697+
const {result} = renderHook(() => useUpcomingTravelReservations());
698+
699+
await waitFor(() => {
700+
expect(result.current).toHaveLength(1);
701+
});
702+
expect(result.current.at(0)?.reservation.reservationID).toBe('PNR_BOUNDARY');
703+
});
704+
705+
it('should return empty array for trip room without tripData', async () => {
706+
const tripRoom = {
707+
reportID: '1003',
708+
ownerAccountID: TEST_ACCOUNT_ID,
709+
type: CONST.REPORT.TYPE.CHAT,
710+
chatType: CONST.REPORT.CHAT_TYPE.TRIP_ROOM,
711+
reportName: 'Trip 1003',
712+
policyID: 'policy1',
713+
} as Report;
714+
715+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1003`, tripRoom);
716+
await waitForBatchedUpdates();
717+
718+
const {result} = renderHook(() => useUpcomingTravelReservations());
719+
720+
await waitFor(() => {
721+
expect(result.current).toEqual([]);
722+
});
723+
});
724+
725+
it('should return empty array for trip room with empty pnrs array', async () => {
726+
const tripRoom = makeTripRoomReport('1004', []);
727+
728+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1004`, tripRoom);
729+
await waitForBatchedUpdates();
730+
731+
const {result} = renderHook(() => useUpcomingTravelReservations());
732+
733+
await waitFor(() => {
734+
expect(result.current).toEqual([]);
735+
});
736+
});
737+
738+
it('should ignore non-trip-room reports', async () => {
739+
const flight = makeAirPnr('PNR_NON_TRIP', daysFromNow(2), daysFromNow(2, 15));
740+
const nonTripReport = {
741+
reportID: '1005',
742+
ownerAccountID: TEST_ACCOUNT_ID,
743+
type: CONST.REPORT.TYPE.CHAT,
744+
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
745+
reportName: 'Policy Room',
746+
policyID: 'policy1',
747+
tripData: {
748+
tripID: 'trip-1005',
749+
payload: {pnrs: [flight]},
750+
},
751+
} as Report;
752+
753+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1005`, nonTripReport);
754+
await waitForBatchedUpdates();
755+
756+
const {result} = renderHook(() => useUpcomingTravelReservations());
757+
758+
await waitFor(() => {
759+
expect(result.current).toEqual([]);
760+
});
761+
});
659762
});

0 commit comments

Comments
 (0)