Skip to content

[Due for payment 2026-05-22] [$250] [BATCH 1] Migrate ConfirmModals & ReanimatedModals #83061

Closed

Description

@blazejkustra

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 OwnerCurrent Issue Owner: @mallenexpensify
Upwork 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

Activity

  1. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Feb 20, 2026
  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Feb 20, 2026
  3. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Feb 20, 2026
  4. melvin-bot commented on Feb 20, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @FitseTLT (External)

  5. melvin-bot commented on Feb 20, 2026

    @melvin-bot

    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.

  6. changed the title [-][BATCH 1] Migrate ConfirmModals & ReanimatedModals[/-] [+][$250] [BATCH 1] Migrate ConfirmModals & ReanimatedModals[/+] on Feb 20, 2026
  7. melvin-bot commented on Feb 20, 2026

    @melvin-bot
  8. blazejkustra commented on Feb 20, 2026

    @blazejkustra
    ContributorAuthor

    Please do not post proposals, since this will be implemented by Software Mansion team or later distributed to externals. Thank you!

  9. MelvinBot commented on Feb 20, 2026

    @MelvinBot
    Contributor

    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 the useConfirmModal hook and 4 ReanimatedModal usages to an afterTransition ref pattern.

    What is the root cause of that problem?

    InteractionManager.runAfterInteractions is a blunt timing mechanism — it schedules callbacks after all pending interactions complete, which is unreliable and deprecated. The new useConfirmModal hook solves this architecturally: showConfirmModal() returns a Promise that resolves after the modal close animation finishes (via ConfirmModalWrapper's two-phase close: setIsVisible(false) triggers the exit animation → onModalHide fires after animation → closeModal() resolves the promise). Any code after await 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 → useConfirmModal hook

    File Status What Remains Recommended Change
    WorkspaceMembersPage.tsx Partially migrated — already uses useConfirmModal InteractionManager at L262 inside removeUsers() clears selection + calls removeMembers Move setSelectedEmployees([]) and removeMembers() to after the await showConfirmModal() (they already run post-confirm, just remove the InteractionManager wrapper)
    ReportParticipantsPage.tsx Partially migrated — already uses useConfirmModal InteractionManager at L177 inside removeUsers() clears selection + search phrase Move setSelectedMembers([]) and clearUserSearchPhrase() to after the await, remove InteractionManager
    RoomMembersPage.tsx Partially migrated — already uses useConfirmModal InteractionManager at L155 inside removeUsers() Same pattern — move cleanup after the await
    WorkspaceCategoriesPage.tsx Not migrated — uses <ConfirmModal> component + state InteractionManager at L370 clears selectedCategories Replace <ConfirmModal> + boolean state with useConfirmModal hook. Move setSelectedCategories([]) after the await
    WorkspaceCategoriesSettingsPage.tsx Not migrated — different pattern InteractionManager at L85 hides CategorySelectorModal after Keyboard.dismiss() This is NOT a confirm modal pattern — it coordinates keyboard dismiss → modal hide. Migrate by using onModalHide callback or removing deferral if keyboard dismiss is synchronous on target platforms
    WorkspaceTagsPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L439 clears selection + conditionally toggles mobile selection mode Replace with useConfirmModal. Move cleanup after await
    WorkspaceViewTagsPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L221 clears selection Replace with useConfirmModal. Move cleanup after await
    WorkspaceTaxesPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L250 clears selection Replace with useConfirmModal. Move cleanup after await
    PolicyDistanceRatesPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L321 clears selection Replace with useConfirmModal. Move cleanup after await
    WorkspacePerDiemPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L267 clears selection Replace with useConfirmModal. Move cleanup after await
    ReportFieldsListValuesPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L195 clears selection Replace with useConfirmModal. Move cleanup after await
    WorkspaceExpensifyCardDetailsPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L94 runs deactivateCardAction() (business logic, not just cleanup). Also has shouldSetModalVisibility={false} + onModalHide for navigation Replace with useConfirmModal. The deactivateCardAction call and navigation go after the await. Pass onModalHide through for navigation if needed
    WorkspaceCompanyCardsSettingsPage.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L88 runs deleteWorkspaceCompanyCardFeed after navigate back Replace with useConfirmModal. Navigation + API call after await
    WorkspaceWorkflowsPage.tsx Partially migrated — uses useConfirmModal for approval toggle InteractionManager at L142 defers fetchData() on mount via useEffect This is a mount-time fetch deferral, NOT a confirm modal. Use ScreenWrapper's onEntryTransitionEnd or remove deferral
    WorkspaceWorkflowsApprovalsEditPage.tsx Partially migrated — uses useConfirmModal for delete InteractionManager at L59 and L73 defer API calls (updateApprovalWorkflow, removeApprovalWorkflow) after Navigation.dismissModal() These are post-navigation deferrals. Use Navigation's transition callbacks or the useConfirmModal promise resolution
    WorkspacesListPage.tsx Not migrated — uses <ConfirmModal> components InteractionManager at L677 clears duplicate workspace state after scrollToIndex The InteractionManager is unrelated to the ConfirmModals. Migrate ConfirmModals to useConfirmModal; replace InteractionManager with requestAnimationFrame or remove
    PopoverReportActionContextMenu.tsx Not migrated — uses <ConfirmModal> + state InteractionManager at L381 delays deleteReportComment after confirm Replace with useConfirmModal. Move delete call after await
    AutoSubmitModal.tsx Not migrated — uses FeatureTrainingModal InteractionManager at L43 delays dismissASAPSubmitExplanation NVP write Use FeatureTrainingModal's onModalHide or the confirm callback's post-transition timing
    useFilesValidation.tsx Not migrated — uses <ConfirmModal> for errors InteractionManager at L108 (resets validation state) and L365 (processes files after error modal) Replace error <ConfirmModal> with useConfirmModal. Put resetValidationState() and file processing after the await
    ContactMethodDetailsPage.tsx Not migrated — uses <ConfirmModal> + state Three InteractionManager calls: L130 (show modal after keyboard dismiss), L215 (refocus form after modal hide), L279 (focus on screen entry) Replace ConfirmModal with useConfirmModal for L130+L215. L279 can use ScreenWrapper's onEntryTransitionEnd. Most complex migration in this batch
    IOURequestStepMerchant.tsx Not migrated InteractionManager at L180 refocuses input after DiscardChangesConfirmation cancel Migrate DiscardChangesConfirmation to use useConfirmModal, put focus after await
    IOURequestStepDescription.tsx Not migrated InteractionManager at L227 refocuses input after DiscardChangesConfirmation cancel Same as Merchant — focus after await

    Section 2 — ReanimatedModal Pattern → afterTransition callback

    Note: afterTransition does not exist yet in the codebase (grep returns zero results). It would need to be implemented as a new capability on ReanimatedModal before these can be migrated.

    File Status What Remains Recommended Change
    FeatureTrainingModal.tsx Not migrated Two InteractionManager calls: L225 (delays modal show until interactions complete) and L351 (delays goBack() + onClose after modal hide) Use ReanimatedModal's onModalHide for L351. L225 needs a show-after-transition mechanism (could use ScreenWrapper's transition callback)
    AvatarCropModal.tsx Not migrated InteractionManager at L324 delays onClose() after crop completes while calling onSave immediately Use ReanimatedModal's onModalHide callback to fire onClose after the close animation
    AttachmentModalHandler/index.ios.ts Not migrated — iOS-only InteractionManager at L12 delays close callback to prevent iOS slide animation glitch Use ReanimatedModal's onModalHide callback. iOS-specific — needs careful testing

    Migration pattern (for Section 1 files not yet migrated):

    // BEFORE:
    const [isModalVisible, setIsModalVisible] = useState(false);
    const handleDelete = () => {
        setIsModalVisible(false);
        InteractionManager.runAfterInteractions(() => {
            setSelectedItems([]);
            deleteItems();
        });
    };
    // + <ConfirmModal isVisible={isModalVisible} onConfirm={handleDelete} ... />
    
    // AFTER:
    const {showConfirmModal} = useConfirmModal();
    const handleDelete = async () => {
        const {action} = await showConfirmModal({
            title: '...', prompt: '...', confirmText: '...', danger: true,
        });
        if (action !== ModalActions.CONFIRM) { return; }
        setSelectedItems([]);
        deleteItems();
    };
    // No <ConfirmModal> in JSX — it's rendered by the global ModalProvider

    What alternative solutions did you explore? (Optional)

    The afterTransition ref 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 on ReanimatedModal's existing onModalHide callback (which already fires after close animation) rather than building a new afterTransition API. The onModalHide callback flows through ReanimatedModal's onCloseCallBack → onDismiss and only fires after the transition completes — which is exactly what InteractionManager.runAfterInteractions was approximating.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this analysis.

  10. melvin-bot commented on Mar 2, 2026

    @melvin-bot

    📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

  11. 36 remaining items

  12. melvin-bot commented on May 15, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  13. melvin-bot commented on May 15, 2026

    @melvin-bot

    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:

    • @Pujan92 requires payment through NewDot Manual Requests
    • @collectioneur does not require payment (Contractor)
  14. melvin-bot commented on May 15, 2026

    @melvin-bot

    @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]

  15. melvin-bot commented on May 16, 2026

    @melvin-bot

    ⚠️ Looks like this issue was linked to a Deploy Blocker here

    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.

  16. melvin-bot commented on May 18, 2026

    @melvin-bot

    @Pujan92 Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  17. melvin-bot commented on May 20, 2026

    @melvin-bot

    @Pujan92 Huh... This is 4 days overdue. Who can take care of this?

  18. collectioneur commented on May 21, 2026

    @collectioneur
    Contributor

    @roryabraham, this issue can be closed since the PR was merged some time ago

  19. melvin-bot commented on May 21, 2026

    @melvin-bot

    Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.

  20. melvin-bot commented on May 22, 2026

    @melvin-bot

    Payment Summary

    Upwork Job

    BugZero Checklist (@mallenexpensify)

    • (if NewFeature) I have created a PR for any necessary HelpDot updates, or I confirmed no updates are necessary.
    • I have verified the correct assignees and roles are listed above and updated the necessary manual offers
    • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants/2024862475238054813/hired)
    • I have verified the PR was not reverted
    • I have applied any discounts due to bugs/regressions introduced by this PR
    • I have paid out the Upwork contracts or cancelled the ones that are incorrect
    • I have verified the payment summary above is correct
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions