Problem
MoneyRequestReportPreviewContent (~800 lines, ~55 hook calls) is a single component that bundles five unrelated concerns in one render body — loading-state derivation, the preview-message animation, the header (name / status / expense count), the transaction carousel, and the hold menu. It's mounted once per REPORT_PREVIEW action inside the chat's ReportActionsList, so many instances are live at the same time.
Because everything shares one render and derived state is drilled down through deep prop chains (the action-button + total row alone relayed ~16 props), any state change has a wide blast radius: a single carousel scroll tick or a pay/approve animation frame re-renders the entire card — including the FlashList and every sibling — not just the part that actually changed.
Solution
Decompose MoneyRequestReportPreviewContent into focused, single-responsibility pieces and remove the prop drilling:
- Extract by concern —
useReportPreviewCarousel and usePreviewMessageAnimation hooks; ReportPreviewHeader, TransactionReportCarousel, ReportPreviewTotal, and a gated renderless ReportPreviewHoldMenu; the shell becomes composition only.
- Remove prop drilling via context — a
MoneyRequestReportPreviewProvider owns all the derivations once and exposes them as separate context slices, one per concern: the report data, the loading/UI state, the carousel list, the carousel scroll state, the animation state, the action callbacks, and the refs. Each component reads only the slice(s) it needs, so a change to one slice (e.g. a carousel scroll or a pay/approve animation) doesn't re-render the consumers of another.
PR
#94563
Issue Owner
Current Issue Owner: @mallenexpensify
Upwork Automation - Do Not Edit
Problem
MoneyRequestReportPreviewContent(~800 lines, ~55 hook calls) is a single component that bundles five unrelated concerns in one render body — loading-state derivation, the preview-message animation, the header (name / status / expense count), the transaction carousel, and the hold menu. It's mounted once perREPORT_PREVIEWaction inside the chat'sReportActionsList, so many instances are live at the same time.Because everything shares one render and derived state is drilled down through deep prop chains (the action-button + total row alone relayed ~16 props), any state change has a wide blast radius: a single carousel scroll tick or a pay/approve animation frame re-renders the entire card — including the FlashList and every sibling — not just the part that actually changed.
Solution
Decompose
MoneyRequestReportPreviewContentinto focused, single-responsibility pieces and remove the prop drilling:useReportPreviewCarouselandusePreviewMessageAnimationhooks;ReportPreviewHeader,TransactionReportCarousel,ReportPreviewTotal, and a gated renderlessReportPreviewHoldMenu; the shell becomes composition only.MoneyRequestReportPreviewProviderowns all the derivations once and exposes them as separate context slices, one per concern: the report data, the loading/UI state, the carousel list, the carousel scroll state, the animation state, the action callbacks, and the refs. Each component reads only the slice(s) it needs, so a change to one slice (e.g. a carousel scroll or a pay/approve animation) doesn't re-render the consumers of another.PR
#94563
Issue Owner
Current Issue Owner: @mallenexpensifyUpwork Automation - Do Not Edit