Proposal: Cache search query parsing to eliminate redundant computation
Background
Expensify's search functionality uses a PEG grammar parser (buildSearchQueryJSON) that converts query strings into structured objects used across the app — search results, navigation tabs, filter bars, and type menus. This is a pure function: same input always produces the same output. In SearchUIUtils.ts, search type menu items expose parsed data through JavaScript getter properties (get searchQueryJSON()) that re-execute the full parser on every property access. There are ~14 such objects, and their properties are accessed by multiple components (NavigationTabBar, SearchTypeMenu, SearchContextProvider) during each render cycle.
Problem
When a user interacts with search-related screens, each render cycle triggers the PEG parser dozens of times with identical query strings via getter property accesses, blocking the JS thread with redundant computation that produces results already computed milliseconds earlier.
Solution
Add a module-level Map cache inside buildSearchQueryJSON, keyed on the query string. Before parsing, check if the result exists in the cache — if so, return it immediately. After parsing, store the result. Cap at 50 entries with oldest-first eviction to bound memory. All callers treat the returned object as read-only, making the cache safe.
Expected Outcome
Flow: create expense fab → add amount → add merchant → create expense → navigate to reports
- Reduce render time across all components from 7,027ms → 5,203ms (-26%)
ManualOpenCreateExpense span: 479ms → 288ms (-40%)
PR: #82931
Proposal: Cache search query parsing to eliminate redundant computation
Background
Expensify's search functionality uses a PEG grammar parser (
buildSearchQueryJSON) that converts query strings into structured objects used across the app — search results, navigation tabs, filter bars, and type menus. This is a pure function: same input always produces the same output. InSearchUIUtils.ts, search type menu items expose parsed data through JavaScript getter properties (get searchQueryJSON()) that re-execute the full parser on every property access. There are ~14 such objects, and their properties are accessed by multiple components (NavigationTabBar,SearchTypeMenu,SearchContextProvider) during each render cycle.Problem
When a user interacts with search-related screens, each render cycle triggers the PEG parser dozens of times with identical query strings via getter property accesses, blocking the JS thread with redundant computation that produces results already computed milliseconds earlier.
Solution
Add a module-level Map cache inside
buildSearchQueryJSON, keyed on the query string. Before parsing, check if the result exists in the cache — if so, return it immediately. After parsing, store the result. Cap at 50 entries with oldest-first eviction to bound memory. All callers treat the returned object as read-only, making the cache safe.Expected Outcome
Flow: create expense fab → add amount → add merchant → create expense → navigate to reports
ManualOpenCreateExpensespan: 479ms → 288ms (-40%)PR: #82931