Background
MoneyRequestReportView renders the money request report screen with the transactions table and the report actions chat feed. Onyx pushes updates to it continuously (chat messages, transaction changes), and the components derive everything they render: sorting, grouping, violations, columns, selection state.
The view is made of two very large components:
MoneyRequestReportActionsList.tsx — 817 lines
MoneyRequestReportTransactionList.tsx — 1138 lines
Problem
When a money request report is open, any Onyx update that touches the report (opening it, sending a message, a background sync) makes both list components re-derive and re-render everything. Measured cost: ~1.5s of total re-render time on report open, ~1.0s per message send cycle.
Solution
Decompose both components into small, single purpose hooks and presentational components that the React Compiler can compile and memoize, so each Onyx update re-runs only the derivation chain it affects.
The complete end state is implemented and measured on branch decompose/money-request-report-transaction-list. Land it as ten small, independently mergeable PRs, each a pure refactor with no behavior change.
The tracks are sequential only because Track B reuses Track A's extraction patterns and perf test setup. Pausing between any two PRs leaves main consistent.
Track A: refactor MoneyRequestReportActionsList
- PR A0: add performance test. The reassure perf test with Onyx seeding, derived value registration, and the mount + update (send message and unrelated report) scenarios.
- PR A1: extract
MoneyRequestReportEmptyStateView. Small presentational component (~45 lines). Trims the empty state branch out of the monolith.
- PR A2: extract
useMoneyRequestReportVisibleActions and adapt useMarkAsRead / useUnreadMarker. Moves the visible actions filtering into a hook and makes the two shared chat hooks consumable from this view.
- PR A3: extract
useMoneyRequestReportPagination. Older and newer action loading cursors, onStartReached and onEndReached.
- PR A4: extract
useMoneyRequestReportScroll. Scroll tracking, floating message counter, viewability, and scroll to latest.
- PR A5: extract
useMoneyRequestReportData.
Track B: refactor MoneyRequestReportTransactionList (after Track A)
- PR B1: extract presentational components.
MoneyRequestReportGroupByButton, MoneyRequestReportTableHeaderRow, MoneyRequestReportListFooter.
- PR B2: extract
useMoneyRequestReportColumns and useMoneyRequestReportLayout. Column visibility and sizing, plus the group by layout state.
- PR B3: extract the transaction data derivation chain.
useMoneyRequestReportSortedTransactions, useMoneyRequestReportGroupedTransactions, useMoneyRequestReportActiveTransactionIDs: sorting, violations, grouping, visual order.
- PR B4: extract
useMoneyRequestReportTransactionSelection and useMoneyRequestReportPendingExpense, then finish wiring. Selection state, pending expense placeholder, and the final unified list controller shape.
Measured impact (iOS simulator)
Warm report open:
ManualOpenReport span: 791ms to 607ms (−23%)
- Total re-render time: 1517ms to 1239ms (−18%)
Message send:
- Total re-render time: 1008ms to 743ms (−26%)
- Track A alone cuts the send and type cycle by ~10.8% (~90ms)
ManualSendMessageVisible span: no significant change (743ms vs 706ms, within noise)
References
Issue Owner
Current Issue Owner: @suneox
Background
MoneyRequestReportViewrenders the money request report screen with the transactions table and the report actions chat feed. Onyx pushes updates to it continuously (chat messages, transaction changes), and the components derive everything they render: sorting, grouping, violations, columns, selection state.The view is made of two very large components:
MoneyRequestReportActionsList.tsx— 817 linesMoneyRequestReportTransactionList.tsx— 1138 linesProblem
When a money request report is open, any Onyx update that touches the report (opening it, sending a message, a background sync) makes both list components re-derive and re-render everything. Measured cost: ~1.5s of total re-render time on report open, ~1.0s per message send cycle.
Solution
Decompose both components into small, single purpose hooks and presentational components that the React Compiler can compile and memoize, so each Onyx update re-runs only the derivation chain it affects.
The complete end state is implemented and measured on branch
decompose/money-request-report-transaction-list. Land it as ten small, independently mergeable PRs, each a pure refactor with no behavior change.The tracks are sequential only because Track B reuses Track A's extraction patterns and perf test setup. Pausing between any two PRs leaves
mainconsistent.Track A: refactor
MoneyRequestReportActionsListMoneyRequestReportEmptyStateView. Small presentational component (~45 lines). Trims the empty state branch out of the monolith.useMoneyRequestReportVisibleActionsand adaptuseMarkAsRead/useUnreadMarker. Moves the visible actions filtering into a hook and makes the two shared chat hooks consumable from this view.useMoneyRequestReportPagination. Older and newer action loading cursors,onStartReachedandonEndReached.useMoneyRequestReportScroll. Scroll tracking, floating message counter, viewability, and scroll to latest.useMoneyRequestReportData.Track B: refactor
MoneyRequestReportTransactionList(after Track A)MoneyRequestReportGroupByButton,MoneyRequestReportTableHeaderRow,MoneyRequestReportListFooter.useMoneyRequestReportColumnsanduseMoneyRequestReportLayout. Column visibility and sizing, plus the group by layout state.useMoneyRequestReportSortedTransactions,useMoneyRequestReportGroupedTransactions,useMoneyRequestReportActiveTransactionIDs: sorting, violations, grouping, visual order.useMoneyRequestReportTransactionSelectionanduseMoneyRequestReportPendingExpense, then finish wiring. Selection state, pending expense placeholder, and the final unified list controller shape.Measured impact (iOS simulator)
Warm report open:
ManualOpenReportspan: 791ms to 607ms (−23%)Message send:
ManualSendMessageVisiblespan: no significant change (743ms vs 706ms, within noise)References
Issue Owner
Current Issue Owner: @suneox