Problem
FloatingActionButtonAndPopover.tsx has grown into a ~770-line monolithic component that owns too many responsibilities:
- 22
useOnyx subscriptions (session, policies, reports, drafts, violations, betas, account, travel, onboarding, etc.)
- 12
useMemo and 11 useCallback hooks for manual memoization
- Menu item construction (~200 lines building a single
menuItems array)
- Quick action state, avatars, title/subtitle derivation
- Permission checks (invoice, travel, delegate access, billing grace period)
- Modal orchestration (redirect-to-Classic, empty report confirmation)
- Keyboard navigation wiring
- Layout rendering (FAB button, receipt button, PopoverMenu with positioning)
- Telemetry span management
This causes several concrete problems:
- Cascading re-renders: Any single Onyx key change (e.g. a policy update, a new transaction draft, a beta flag flip) triggers a re-render of the entire component, re-evaluating all 22 subscriptions and rebuilding all 12 memoized values -- even when only one menu item cares about the changed data.
- Poor maintainability: Adding or modifying a single menu item (e.g. adding a new FAB action) requires understanding ~770 lines of interleaved concerns. The component mixes data fetching, visibility logic, navigation, modals, and rendering in a single function body.
- Manual memoization burden: The 12
useMemo + 11 useCallback calls exist solely to mitigate the re-render problem caused by co-locating unrelated data in one component. This is the opposite of our React Compiler strategy, which works best when components are small and focused.
- Testing difficulty: The component cannot be unit-tested in isolation per feature -- testing invoice menu item visibility requires mounting the entire FAB with all 22 Onyx subscriptions mocked.
Proposal
Decompose into a composable architecture where:
- Each FAB menu item (Expense, Invoice, Travel, Quick Action, etc.) becomes a self-contained component that fetches its own data, owns its visibility logic, and registers itself with a lightweight context for keyboard navigation
- The parent component shrinks to ~80 lines of pure composition
- Manual memoization (
useCallback, useMemo) is removed in favor of React Compiler
- A
FABMenuContext provides keyboard navigation registration without prop drilling
This follows the Clean React coding standards and aligns with our React Compiler adoption strategy.
Issue Owner
Current Issue Owner: @mallenexpensify
Problem
FloatingActionButtonAndPopover.tsxhas grown into a ~770-line monolithic component that owns too many responsibilities:useOnyxsubscriptions (session, policies, reports, drafts, violations, betas, account, travel, onboarding, etc.)useMemoand 11useCallbackhooks for manual memoizationmenuItemsarray)This causes several concrete problems:
useMemo+ 11useCallbackcalls exist solely to mitigate the re-render problem caused by co-locating unrelated data in one component. This is the opposite of our React Compiler strategy, which works best when components are small and focused.Proposal
Decompose into a composable architecture where:
useCallback,useMemo) is removed in favor of React CompilerFABMenuContextprovides keyboard navigation registration without prop drillingThis follows the Clean React coding standards and aligns with our React Compiler adoption strategy.
Issue Owner
Current Issue Owner: @mallenexpensify