Skip to content

Commit 9cb1ede

Browse files
committed
Split cleanup into unmount-only effect to prevent null flash on re-render
When visualOrderTransactionIDs changes by reference only (same content), the previous combined effect would clear → re-set the IDs, causing a brief null state in Onyx. By separating clearActiveTransactionIDs into its own unmount-only effect, referential re-renders only call setActiveTransactionIDs (which the idempotent guard skips) without touching the clear path. Made-with: Cursor
1 parent ac0b517 commit 9cb1ede

2 files changed

Lines changed: 16 additions & 11 deletions

File tree

src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,12 @@ function MoneyRequestReportTransactionList({
286286
return;
287287
}
288288
setActiveTransactionIDs(visualOrderTransactionIDs);
289-
return () => {
290-
clearActiveTransactionIDs();
291-
};
292289
}, [visualOrderTransactionIDs]);
293290

291+
useEffect(() => {
292+
return () => clearActiveTransactionIDs();
293+
}, []);
294+
294295
const sortedTransactionsMap = useMemo(() => {
295296
const map = new Map<string, OnyxTypes.Transaction>();
296297
for (const transaction of sortedTransactions) {

tests/unit/MoneyRequestReportTransactionListActiveTransactionIDsTest.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ function useActiveTransactionIDsEffect(visualOrderTransactionIDs: string[]) {
3535
return;
3636
}
3737
setActiveTransactionIDs(visualOrderTransactionIDs);
38-
return () => {
39-
clearActiveTransactionIDs();
40-
};
4138
}, [visualOrderTransactionIDs]);
39+
40+
useEffect(() => {
41+
return () => clearActiveTransactionIDs();
42+
}, []);
4243
}
4344

4445
describe('MoneyRequestReportTransactionList - Active Transaction IDs Effect', () => {
@@ -107,7 +108,7 @@ describe('MoneyRequestReportTransactionList - Active Transaction IDs Effect', ()
107108
expect(mockClearActiveTransactionIDs).toHaveBeenCalledTimes(1);
108109
});
109110

110-
it('should NOT call clearActiveTransactionIDs on unmount when route was NOT SEARCH_REPORT', () => {
111+
it('should call clearActiveTransactionIDs on unmount even when route was NOT SEARCH_REPORT', () => {
111112
// Given the focused route is NOT SEARCH_REPORT
112113
mockFindFocusedRoute.mockReturnValue({name: 'SomeOtherRoute', key: 'test-key'});
113114

@@ -118,8 +119,8 @@ describe('MoneyRequestReportTransactionList - Active Transaction IDs Effect', ()
118119

119120
unmount();
120121

121-
// Then clearActiveTransactionIDs should NOT be called (since the effect returned early)
122-
expect(mockClearActiveTransactionIDs).not.toHaveBeenCalled();
122+
// Then clearActiveTransactionIDs should still be called (cleanup runs on unmount regardless of route)
123+
expect(mockClearActiveTransactionIDs).toHaveBeenCalledTimes(1);
123124
});
124125

125126
it('should update active transaction IDs when the list changes', () => {
@@ -145,7 +146,7 @@ describe('MoneyRequestReportTransactionList - Active Transaction IDs Effect', ()
145146
expect(mockSetActiveTransactionIDs).toHaveBeenLastCalledWith(newTransactionIDs);
146147
});
147148

148-
it('should call setActiveTransactionIDs on reference change (idempotent guard is in the action layer)', () => {
149+
it('should call setActiveTransactionIDs on reference change without clearing first (idempotent guard is in the action layer)', () => {
149150
// Given the focused route is SEARCH_REPORT
150151
mockFindFocusedRoute.mockReturnValue({name: SCREENS.RIGHT_MODAL.SEARCH_REPORT, key: 'test-key'});
151152

@@ -162,9 +163,12 @@ describe('MoneyRequestReportTransactionList - Active Transaction IDs Effect', ()
162163
const sameContentNewArray = ['trans1', 'trans2'];
163164
rerender({ids: sameContentNewArray});
164165

165-
// Then the effect fires (new reference), but setActiveTransactionIDs internally skips the Onyx write
166+
// Then the effect fires (new reference), but setActiveTransactionIDs internally skips the Onyx write.
167+
// Crucially, clearActiveTransactionIDs is NOT called on re-render (only on unmount),
168+
// preventing the null→same-IDs flash.
166169
expect(mockSetActiveTransactionIDs).toHaveBeenCalledTimes(2);
167170
expect(mockSetActiveTransactionIDs).toHaveBeenLastCalledWith(sameContentNewArray);
171+
expect(mockClearActiveTransactionIDs).not.toHaveBeenCalled();
168172
});
169173

170174
it('should handle empty transaction IDs array', () => {

0 commit comments

Comments
 (0)