Repository navigation
Description
Modal After Transition
Refer to README.md for more information what's the overall strategy and why we're migrating away from InteractionManager.runAfterInteractions.
Strategy
Migrate to useConfirmModal hook
The showConfirmModal function returned by useConfirmModal returns a Promise that resolves after the modal close transition completes (the underlying closeModal is called inside onModalHide, which fires after the animation finishes). This means any code that awaits the result already runs post-transition — no explicit afterTransition callback is needed.
const {showConfirmModal} = useConfirmModal();
const {action} = await showConfirmModal({
title: 'Delete items?',
prompt: 'This cannot be undone.',
confirmText: 'Delete',
danger: true,
});
if (action !== ModalActions.CONFIRM) {
return;
}
// This runs after the modal close transition — cleanup here
setSelectedItems([]);
deleteItems();Instead of:
setModalVisible(false);
InteractionManager.runAfterInteractions(() => {
// cleanup here
});
For ReanimatedModal usages, use ref pattern (ref.current.open({afterTransition: callback})) with afterTransition to defer work until after the modal close animation
1. ConfirmModal Pattern (migrate to useConfirmModal hook)
Nearly all workspace pages follow the same pattern: user confirms an action via a modal, then selections are cleared after the modal closes. Migrate from inline <ConfirmModal> component + InteractionManager to the useConfirmModal hook, whose promise resolves after the close transition.
| File | Line | Current | Migration | PR |
|---|---|---|---|---|
WorkspaceMembersPage.tsx |
272 | setSelectedEmployees([]) + remove |
Migrate to useConfirmModal hook |
#54178 |
ReportParticipantsPage.tsx |
251 | setSelectedMembers([]) |
Migrate to useConfirmModal hook |
#50488 |
ReportParticipantsPage.tsx |
446 | Same pattern | Migrate to useConfirmModal hook |
#50488 |
RoomMembersPage.tsx |
155 | setSelectedMembers([]) |
Migrate to useConfirmModal hook |
#50488 |
WorkspaceCategoriesPage.tsx |
346 | Category bulk action cleanup | Migrate to useConfirmModal hook |
#60023 |
WorkspaceCategoriesSettingsPage.tsx |
346 | Category settings cleanup | Migrate to useConfirmModal hook |
#56792 |
WorkspaceTagsPage.tsx |
437 | Tag bulk action cleanup | Migrate to useConfirmModal hook |
#60023 |
WorkspaceViewTagsPage.tsx |
221 | Tag view action cleanup | Migrate to useConfirmModal hook |
#82523 |
WorkspaceTaxesPage.tsx |
250 | Tax bulk action cleanup | Migrate to useConfirmModal hook |
#60023 |
PolicyDistanceRatesPage.tsx |
317 | Distance rate action cleanup | Migrate to useConfirmModal hook |
#60023 |
WorkspacePerDiemPage.tsx |
267 | Per diem action cleanup | Migrate to useConfirmModal hook |
#60023 |
ReportFieldsListValuesPage.tsx |
195 | Report fields cleanup | Migrate to useConfirmModal hook |
#60023 |
WorkspaceExpensifyCardDetailsPage.tsx |
94 | Card details cleanup | Migrate to useConfirmModal hook |
#74530 |
WorkspaceCompanyCardsSettingsPage.tsx |
88 | Card settings cleanup | Migrate to useConfirmModal hook |
#57373 |
WorkspaceWorkflowsPage.tsx |
141 | Workflows action cleanup | Migrate to useConfirmModal hook |
#56932 |
WorkspaceWorkflowsApprovalsEditPage.tsx |
59 | Approvals edit cleanup | Migrate to useConfirmModal hook |
#52163 |
WorkspaceWorkflowsApprovalsEditPage.tsx |
73 | Approvals edit cleanup | Migrate to useConfirmModal hook |
#48618 |
WorkspacesListPage.tsx |
639 | Workspaces list action cleanup | Migrate to useConfirmModal hook |
#69146 |
PopoverReportActionContextMenu.tsx |
374 | deleteReportComment() after confirm |
Migrate to useConfirmModal hook |
#66791 |
AutoSubmitModal.tsx |
43 | dismissASAPSubmitExplanation() on modal close |
Migrate to useConfirmModal hook |
#63893 |
useFilesValidation.tsx |
108 | InteractionManager deferring file validation | Migrate to useConfirmModal hook |
#65316 |
useFilesValidation.tsx |
365 | InteractionManager deferring file validation | Migrate to useConfirmModal hook |
#65316 |
ContactMethodDetailsPage.tsx |
130 | Open delete modal after keyboard dismiss | Migrate to useConfirmModal hook |
#35305 |
IOURequestStepMerchant.tsx |
181 | Merchant submit + keyboard dismiss | Migrate to useConfirmModal hook |
#67010 |
IOURequestStepDescription.tsx |
226 | Description submit + keyboard dismiss | Migrate to useConfirmModal hook |
#67010 |
2. ReanimatedModal Pattern (afterTransition callback)
These usages involve ReanimatedModal's ref pattern to defer work until after the modal close animation.
| File | Line | Current | Migration | PR |
|---|---|---|---|---|
FeatureTrainingModal.tsx |
224 | setIsModalVisible based on disabled state |
Use ReanimatedModal ref approach with afterTransition | #57649 |
FeatureTrainingModal.tsx |
352 | goBack + onClose after setting invisible | Use ReanimatedModal ref approach with afterTransition | #53225 |
AvatarCropModal/AvatarCropModal.tsx |
324 | InteractionManager deferring onClose | Use ReanimatedModal ref approach with afterTransition | #66890 |
AttachmentModalHandler/index.ios.ts |
12 | iOS: execute close callback after interaction | Use ReanimatedModal ref approach with afterTransition | #53108 |
Issue Owner
Current Issue Owner: @mallenexpensifyUpwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022024862475238054813
- Upwork Job ID: 2024862475238054813
- Last Price Increase: 2026-03-16
- Automatic offers:
- FitseTLT | Reviewer | 110867972
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
melvin-bot commentedon Feb 20, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @FitseTLT (
External)melvin-bot commentedon Feb 20, 2026
Triggered auto assignment to @jliexpensify (
Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.[-][BATCH 1] Migrate ConfirmModals & ReanimatedModals[/-][+][$250] [BATCH 1] Migrate ConfirmModals & ReanimatedModals[/+]melvin-bot commentedon Feb 20, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022024862475238054813
blazejkustra commentedon Feb 20, 2026
Please do not post proposals, since this will be implemented by Software Mansion team or later distributed to externals. Thank you!
MelvinBot commentedon Feb 20, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
The codebase uses
InteractionManager.runAfterInteractions(now deprecated) across many files to defer work until after modal close animations complete. This issue tracks migrating 25 ConfirmModal usages to theuseConfirmModalhook and 4 ReanimatedModal usages to anafterTransitionref pattern.What is the root cause of that problem?
InteractionManager.runAfterInteractionsis a blunt timing mechanism — it schedules callbacks after all pending interactions complete, which is unreliable and deprecated. The newuseConfirmModalhook solves this architecturally:showConfirmModal()returns a Promise that resolves after the modal close animation finishes (viaConfirmModalWrapper's two-phase close:setIsVisible(false)triggers the exit animation →onModalHidefires after animation →closeModal()resolves the promise). Any code afterawait showConfirmModal()is guaranteed to run post-transition.What changes do you think we should make in order to solve the problem?
Based on code investigation, here is the current migration status and recommended changes for every file listed in this issue:
Section 1 — ConfirmModal Pattern →
useConfirmModalhookWorkspaceMembersPage.tsxuseConfirmModalInteractionManagerat L262 insideremoveUsers()clears selection + callsremoveMemberssetSelectedEmployees([])andremoveMembers()to after theawait showConfirmModal()(they already run post-confirm, just remove theInteractionManagerwrapper)ReportParticipantsPage.tsxuseConfirmModalInteractionManagerat L177 insideremoveUsers()clears selection + search phrasesetSelectedMembers([])andclearUserSearchPhrase()to after the await, removeInteractionManagerRoomMembersPage.tsxuseConfirmModalInteractionManagerat L155 insideremoveUsers()WorkspaceCategoriesPage.tsx<ConfirmModal>component + stateInteractionManagerat L370 clearsselectedCategories<ConfirmModal>+ boolean state withuseConfirmModalhook. MovesetSelectedCategories([])after the awaitWorkspaceCategoriesSettingsPage.tsxInteractionManagerat L85 hidesCategorySelectorModalafterKeyboard.dismiss()onModalHidecallback or removing deferral if keyboard dismiss is synchronous on target platformsWorkspaceTagsPage.tsx<ConfirmModal>+ stateInteractionManagerat L439 clears selection + conditionally toggles mobile selection modeuseConfirmModal. Move cleanup after awaitWorkspaceViewTagsPage.tsx<ConfirmModal>+ stateInteractionManagerat L221 clears selectionuseConfirmModal. Move cleanup after awaitWorkspaceTaxesPage.tsx<ConfirmModal>+ stateInteractionManagerat L250 clears selectionuseConfirmModal. Move cleanup after awaitPolicyDistanceRatesPage.tsx<ConfirmModal>+ stateInteractionManagerat L321 clears selectionuseConfirmModal. Move cleanup after awaitWorkspacePerDiemPage.tsx<ConfirmModal>+ stateInteractionManagerat L267 clears selectionuseConfirmModal. Move cleanup after awaitReportFieldsListValuesPage.tsx<ConfirmModal>+ stateInteractionManagerat L195 clears selectionuseConfirmModal. Move cleanup after awaitWorkspaceExpensifyCardDetailsPage.tsx<ConfirmModal>+ stateInteractionManagerat L94 runsdeactivateCardAction()(business logic, not just cleanup). Also hasshouldSetModalVisibility={false}+onModalHidefor navigationuseConfirmModal. ThedeactivateCardActioncall and navigation go after the await. PassonModalHidethrough for navigation if neededWorkspaceCompanyCardsSettingsPage.tsx<ConfirmModal>+ stateInteractionManagerat L88 runsdeleteWorkspaceCompanyCardFeedafter navigate backuseConfirmModal. Navigation + API call after awaitWorkspaceWorkflowsPage.tsxuseConfirmModalfor approval toggleInteractionManagerat L142 defersfetchData()on mount viauseEffectScreenWrapper'sonEntryTransitionEndor remove deferralWorkspaceWorkflowsApprovalsEditPage.tsxuseConfirmModalfor deleteInteractionManagerat L59 and L73 defer API calls (updateApprovalWorkflow,removeApprovalWorkflow) afterNavigation.dismissModal()useConfirmModalpromise resolutionWorkspacesListPage.tsx<ConfirmModal>componentsInteractionManagerat L677 clears duplicate workspace state afterscrollToIndexuseConfirmModal; replace InteractionManager withrequestAnimationFrameor removePopoverReportActionContextMenu.tsx<ConfirmModal>+ stateInteractionManagerat L381 delaysdeleteReportCommentafter confirmuseConfirmModal. Move delete call after awaitAutoSubmitModal.tsxFeatureTrainingModalInteractionManagerat L43 delaysdismissASAPSubmitExplanationNVP writeFeatureTrainingModal'sonModalHideor the confirm callback's post-transition timinguseFilesValidation.tsx<ConfirmModal>for errorsInteractionManagerat L108 (resets validation state) and L365 (processes files after error modal)<ConfirmModal>withuseConfirmModal. PutresetValidationState()and file processing after the awaitContactMethodDetailsPage.tsx<ConfirmModal>+ stateInteractionManagercalls: L130 (show modal after keyboard dismiss), L215 (refocus form after modal hide), L279 (focus on screen entry)useConfirmModalfor L130+L215. L279 can useScreenWrapper'sonEntryTransitionEnd. Most complex migration in this batchIOURequestStepMerchant.tsxInteractionManagerat L180 refocuses input afterDiscardChangesConfirmationcancelDiscardChangesConfirmationto useuseConfirmModal, put focus after awaitIOURequestStepDescription.tsxInteractionManagerat L227 refocuses input afterDiscardChangesConfirmationcancelSection 2 — ReanimatedModal Pattern →
afterTransitioncallbackNote:
afterTransitiondoes not exist yet in the codebase (grep returns zero results). It would need to be implemented as a new capability onReanimatedModalbefore these can be migrated.FeatureTrainingModal.tsxInteractionManagercalls: L225 (delays modal show until interactions complete) and L351 (delaysgoBack()+onCloseafter modal hide)ReanimatedModal'sonModalHidefor L351. L225 needs a show-after-transition mechanism (could useScreenWrapper's transition callback)AvatarCropModal.tsxInteractionManagerat L324 delaysonClose()after crop completes while callingonSaveimmediatelyReanimatedModal'sonModalHidecallback to fireonCloseafter the close animationAttachmentModalHandler/index.ios.tsInteractionManagerat L12 delays close callback to prevent iOS slide animation glitchReanimatedModal'sonModalHidecallback. iOS-specific — needs careful testingMigration pattern (for Section 1 files not yet migrated):
What alternative solutions did you explore? (Optional)
The
afterTransitionref pattern mentioned in the issue description for Section 2 does not exist yet in the codebase. An alternative for those 4 files would be to rely onReanimatedModal's existingonModalHidecallback (which already fires after close animation) rather than building a newafterTransitionAPI. TheonModalHidecallback flows throughReanimatedModal'sonCloseCallBack→onDismissand only fires after the transition completes — which is exactly whatInteractionManager.runAfterInteractionswas approximating.Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this analysis.
melvin-bot commentedon Mar 2, 2026
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
36 remaining items
melvin-bot commentedon May 15, 2026
Reviewinglabel has been removed, please complete the "BugZero Checklist".melvin-bot commentedon May 15, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.74-7 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-05-22. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon May 15, 2026
@Pujan92 @mallenexpensify The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
melvin-bot commentedon May 16, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
melvin-bot commentedon May 18, 2026
@Pujan92 Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
melvin-bot commentedon May 20, 2026
@Pujan92 Huh... This is 4 days overdue. Who can take care of this?
collectioneur commentedon May 21, 2026
@roryabraham, this issue can be closed since the PR was merged some time ago
melvin-bot commentedon May 21, 2026
Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.
melvin-bot commentedon May 22, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)