[Payment due @situchan] [No QA] Remove manual memoization from 6 RC-failing files#88370
[Payment due @situchan] [No QA] Remove manual memoization from 6 RC-failing files#88370roryabraham merged 12 commits intomainfrom
Conversation
|
npm has a |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
React Compiler handles the memoization; removing the manual useMemo resolves the 'Existing memoization could not be preserved' RC compliance failure. Part of #68765. Made-with: Cursor
…cture hooks Removes the useCallback wrapping renderComponent so the file passes the React Compiler 'Existing memoization could not be preserved' check, and destructures useState/useTransition from the React import for consistency with useRef. Part of #68765. Made-with: Cursor
…ssage Removes the useCallback around onMessage and restructures the try/catch so it no longer contains a logical-or value block (both unsupported by React Compiler). Also groups Onyx reads together. Part of #68765. Made-with: Cursor
…nline loop Removes four useMemos and one useCallback, replaces the map/filter/sort chain with a plain for…of loop, reorders the component body into canonical sections (onyx → refs/state → derived → effects → handlers → early returns → JSX), and drops the useCallback wraps inside useFocusEffect. Part of #68765. Made-with: Cursor
Removes all five useCallback wraps. The two Onyx selectors are now plain inline const functions (this file is their only caller), and the event handlers are plain arrow functions. Part of #68765. Made-with: Cursor
3390092 to
829c388
Compare
…implify effects Removes the file-wide @typescript-eslint/no-unused-vars disable and deletes the bindings it was hiding (usePolicy, getRateID, currentTransaction, lodashIsEmpty), removes three useMemos and all useCallbacks, and groups Onyx reads together. Deletes the 'clear formError when selectedTab changes' useEffect entirely: formError is event-driven (set on invalid submit, cleared by NumberWithSymbolForm's onInputChange on any keystroke), so the tab-switch reset was unnecessary and triggered react-hooks/set-state-in-effect. This also removes the unused isLoadingSelectedTab / isLoadingOnyxValue machinery. The remaining useEffect that syncs the imperative NumberWithSymbolForm child with the React-owned distance value is documented as a legitimate external-system sync. Part of #68765. Made-with: Cursor
Accounts for the 6 warnings removed by the RC memoization cleanup. Part of #68765. Made-with: Cursor
829c388 to
59718f8
Compare
|
@marcochavezf Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@JmillsExpensify no product changes, just a code refactor |
|
Does this require C+ review? |
|
@situchan sure! |
|
🤖 Code Review — PR #88370 Overall this is a clean refactor. The React Compiler should auto-memoize the callbacks and values based on their captured dependencies, making the manual 1.
|
Replaces two full-collection useOnyx subscriptions (TRANSACTION, REPORT) plus the derived boolean call with a single selector-based hook, so the view only re-renders when the hasMultipleSplits boolean actually changes. Made-with: Cursor
|
Sorry missed this |
|
Signed commits failing but seems unrelated |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppandroid.movAndroid: mWeb Chromemchrome.moviOS: HybridAppios.moviOS: mWeb Safarimsafari.movMacOS: Chrome / Safariweb.mov |
|
Sometimes resolve duplicates doesn't work. Not reliably reproduce ios.mov |
|
Sometimes not found page briefly shows when viewing duplicated expense. This is also not reliably reproducible. Screen.Recording.2026-04-29.at.10.54.05.AM.mov |
| }; | ||
| }, []), | ||
| ); | ||
| useFocusEffect(() => { |
There was a problem hiding this comment.
Just to confirm it's safe to remove useCallback inside useFocusEffect as well?
|
I won't block on above issues sine they're not consistent and I have no reliable repro steps. |
| useEffect(() => { | ||
| if (isLoadingSelectedTab) { | ||
| return; | ||
| } | ||
| // eslint-disable-next-line react-hooks/set-state-in-effect -- Clear form error when tab changes | ||
| setFormError(''); | ||
| }, [selectedTab, isLoadingSelectedTab]); |
There was a problem hiding this comment.
@MelvinBot find PR which added this effect. Just to make sure we don't re-introduce what this fixed.
There was a problem hiding this comment.
Form error isn't cleared when tab changes
@roryabraham I think this is valid regression. This doesn't happen on staging.
Screen.Recording.2026-04-29.at.11.38.26.AM.mov
There was a problem hiding this comment.
hmmm I've thought about it more and I don't think it's actually a bug. It just continues to show the same error that was already on the page. It's not actually stale - there was an error, and they didn't enter a valid amount to clear it. I'm not sure why it would be expected for the error to clear in that case.
There was a problem hiding this comment.
ok but QA might report this as inconsistency as the error clears on other pages like manual request tab:
inconsistency.mov
There was a problem hiding this comment.
I think it's fine from a user perspective, and shouldn't block the regular create request flow
|
The The effect resets validation errors when the user switches between distance request tabs (e.g., Manual vs Map), so that stale errors from one tab don't persist when switching to another. |
|
@situchan please take another look and let me know if you see anything else |
|
🎯 @situchan, thanks for reviewing and testing this PR! 🎉 A payment issue will be created for your review once this PR is deployed to production. If payment is not needed (e.g., regression PR review fix etc), react with 👎 to this comment to prevent the payment issue from being created. |
|
@roryabraham looks like this was merged without a test passing. Please add a note explaining why this was done and remove the |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
1 similar comment
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @roryabraham has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/roryabraham in version: 9.3.65-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are needed for this PR. It's a behavior-neutral refactor (removing manual memoization for React Compiler compliance) with no user-facing changes to features, UI, or terminology. |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.3.65-6 🚀
|
|
🤖 Payment issue created: #89504 |

Explanation of Change
This PR addresses the six files that were failing the React Compiler compliance check with
Existing memoization could not be preserved. Each of these had their manual memoization removed, are now compiling with React Compiler, and were organized to group like items together.Lowered
--max-warningsaccordingly.Fixed Issues
part of #68765
Tests
Offline tests
QA Steps
[No QA] — behavior-neutral refactor.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari