[Payment due @abzokhattab] Refactor ReanimatedModal/index.tsx to follow Rules of React and compile with React Compiler#90358
Conversation
isTransitioning can be derived as isVisible !== isContainerOpen, removing the need for a separate state variable and the effect that set it. isVisibleState was redundant - it always mirrored the isVisible prop with a one-render lag. Both are replaced with direct references to the prop and the derived expression. Co-authored-by: Cursor <cursoragent@cursor.com>
useCallback and useMemo wrappers are unnecessary since the component compiles with React Compiler, which handles memoization automatically. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@carlosmiceli 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] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 173165ee7d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const isBackdropMounted = isVisibleState || ((isTransitioning || isContainerOpen !== isVisibleState) && getPlatform() === CONST.PLATFORM.WEB); | ||
| const modalVisibility = isVisibleState || isTransitioning || isContainerOpen !== isVisibleState; | ||
| const isBackdropMounted = isVisible || (isTransitioning && getPlatform() === CONST.PLATFORM.WEB); | ||
| const modalVisibility = isVisible || isTransitioning; |
There was a problem hiding this comment.
Preserve the closing transition when open is cancelled
If a modal is dismissed while its enter animation is still running, onOpenCallBack has not set isContainerOpen yet, so both isVisible and the derived isTransitioning become false. That makes modalVisibility false immediately and skips the normal Container close callback path; on Android this is the path that calls onModalHide() because Modal.onDismiss is documented here as unreliable. This can leave modal cleanup/focus state out of sync when users press the backdrop/back button quickly during opening.
Useful? React with 👍 / 👎.
|
@MelvinBot review the PR |
PR ReviewClean, well-motivated refactor. The React patterns are correct and the commit-by-commit breakdown makes the reasoning easy to follow. A few observations: Looks good
Worth verifyingContainer rendering now gates on Minor nit
⚡ Reviewed by MelvinBot |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-05-13.at.00.21.50.movAndroid: mWeb ChromeScreen.Recording.2026-05-13.at.00.23.29.moviOS: HybridAppScreen.Recording.2026-05-13.at.00.07.25.moviOS: mWeb SafariScreen.Recording.2026-05-13.at.00.08.19.movMacOS: Chrome / SafariScreen.Recording.2026-05-13.at.00.25.47.mov |
abzokhattab
left a comment
There was a problem hiding this comment.
The changes look good to me ... all QA tests pass
|
🎯 @abzokhattab, 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. |
|
🚀 Deployed to staging by https://github.com/roryabraham in version: 9.3.73-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. The changes are purely internal code refactoring (React Compiler compliance, effect cleanup, removing redundant state) with no user-facing behavior or UI changes. |
|
🚀 Deployed to staging by https://github.com/roryabraham in version: 9.3.73-0 🚀
Bundle Size Analysis (Sentry): |
|
Deploy Blocker #90438 was identified to be related to this PR. |
|
Deploy Blocker #90442 was identified to be related to this PR. |
|
Deploy Blocker #90465 was identified to be related to this PR. |
|
Deploy Blocker #90463 was identified to be related to this PR. |
|
Deploy Blocker #90510 was identified to be related to this PR. |
…tent flash When isVisible becomes false, isTransitioning is true until the exit animation completes. Rendering containerView with modalVisibility (isVisible || isTransitioning) instead of isVisible alone ensures the content stays mounted through the full fade-out, matching the previous isVisibleState behavior that was removed in Expensify#90358. Fixes Expensify#90510
|
Deploy Blocker #90527 was identified to be related to this PR. |
…dal-react-compiler Revert "Refactor ReanimatedModal/index.tsx to follow Rules of React and compile with React Compiler" (#90358)
|
🚀 Deployed to production by https://github.com/Beamanator in version: 9.3.73-4 🚀
|
|
🤖 Payment issue created: #90566 |
|
🚀 Deployed to staging by https://github.com/roryabraham in version: 9.3.74-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. The changes are purely internal refactoring of the |
Explanation of Change
Refactors
src/components/Modal/ReanimatedModal/index.tsxto follow the Rules of React, eliminate unnecessary effects, and compile cleanly with React Compiler.Changes made (each as a separate commit):
Group effects by responsibility — Split the original monolithic
useEffect(which handled external handles, lifecycle callbacks, and state updates simultaneously) into three focused effects, each doing one thing. Moved the unmount cleanup into the same effect that creates the handles, co-locating creation with cleanup.Remove setState from unmount cleanup —
setIsVisibleState(false)andsetIsContainerOpen(false)in the cleanup effect were anti-patterns: on true unmount the state is discarded anyway, and in Strict Mode's simulated unmount/remount cycle they caused spurious extra lifecycle callbacks.Fix stale closure with
useEffectEvent— The lifecycle callback effect (onModalWillShow/onModalWillHide) was suppressingreact-hooks/exhaustive-depsto hide a stale closure bug. Replaced withuseEffectEvent, which always reads the latest callback values without requiring them as reactive dependencies.Derive
isTransitioningand eliminateisVisibleState—isTransitioningis exactlyisVisible !== isContainerOpenand can be derived during rendering instead of maintained as state.isVisibleStatewas redundant state that mirrored theisVisibleprop with a one-render lag. Eliminating both removes auseEffectthat calledsetStatein response to prop changes (a "you might not need an effect" anti-pattern flagged by both the ESLint rule and the React docs).Remove manual memoization —
useCallbackanduseMemowrappers are unnecessary since the component compiles with React Compiler, which handles memoization automatically.Sort component internals — Grouped declarations in the conventional order: hooks → refs → derived values → callbacks → effects → JSX variables.
Fixed Issues
(partial) #68765
Tests
Offline tests
Same as tests — modal open/close behavior is not network-dependent.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand 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.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov