Skip to content

SheetModal: Fix - Dismiss bottom sheet on background instead of only clearing its content - #2406

Open
cmgustavo wants to merge 1 commit into
bitpay:developfrom
cmgustavo:bug/sheet-modal-background-dismiss-01
Open

cmgustavo wants to merge 1 commit into
bitpay:developfrom
cmgustavo:bug/sheet-modal-background-dismiss-01

Conversation

@cmgustavo

@cmgustavo cmgustavo commented Sep 18, 2026

Copy link
Copy Markdown
Member

RN-2975

@JohnathanWhite JohnathanWhite left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanism described here is real — I traced it. Clearing isModalVisible in the background handler short-circuits the !isVisible && isModalVisible guard in the effect below, so the imperative dismiss() never runs and the native sheet stays presented while React thinks it's gone. Skipping that state clear for bottom-sheet is the right shape of fix.

The new spec is a true regression test: with the fix reverted, dismisses the sheet when backgrounded, not just its content fails with 0 dismiss calls instead of 1. Both tests pass with the fix in.

One gap worth a follow-up (not a blocker for this PR). After this change, native dismissal depends entirely on the parent flipping isVisible in response to onBackdropPress. BottomNotification deliberately makes that callback a no-op when enableBackdropDismiss is false:

const handleBackdropPress = useCallback(() => {
  if (enableBackdropDismiss) {
    dispatch(AppActions.dismissBottomNotificationModal());
    ...
  }
}, [enableBackdropDismiss, dispatch, onBackdropDismiss]);

So for those callers — Passkeys.tsx:230, Theme.tsx:54, ThorswapDetails.tsx:224 — backgrounding the app now changes nothing at all and the sheet stays presented. I wrote a throwaway test for that case and got 0 dismiss() calls both before and after this patch, so it's a pre-existing gap rather than something this PR breaks. Worth its own ticket: if the point of the background path is keeping sheet content out of the app-switcher snapshot, the component should dismiss imperatively on background instead of trusting the caller to flip the controlled prop.

Related, also pre-existing: when Gorhom dismisses on its own, handleDismiss doesn't sync isModalVisible back to false, so the two can disagree if a caller leaves isVisible true.

Verified and fine:

  • Dependency array is complete — isVisible, fullscreen, onBackdropPress, modalLibrary are all there, and the effect re-subscribes the AppState listener on change, so no stale closure.
  • The react-native-modal branch and fullscreen sheets are genuinely untouched.
  • Rapid close/reopen still works — the isDismissingRef / pendingOpenRef queueing is undisturbed.
  • Spec hygiene is fine: restoreAllMocks covers both the prototype spies and the AppState.addEventListener spy, and the rerender/tree self-reference is lazily evaluated so it's valid.

One note on the test's reach: it spies on the @gorhom/bottom-sheet stub from test/setup.js, not the real class. That's the right call for asserting SheetModal's imperative calls, but it validates nothing about native lifecycle or unmount-while-backgrounded.

LGTM with the follow-up filed separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants