Repository navigation
Description
Version
v5 (@gorhom/bottom-sheet 5.2.14, also current master)
Reanimated Version
v4 (react-native-reanimated 4.5.1, react-native-worklets 0.10.1; the template only lists v3)
Gesture Handler Version
v2 (2.32.0)
Platforms
Android (tested on a real device in Expo Go SDK 57, and in a release build of our app). The faulty logic is in JS, so iOS should behave the same.
What happened?
A BottomSheetModal with enableDismissOnClose={false} that has been closed (index -1) opens again by itself after a second modal is presented and then dismissed.
This is the second scenario in #1561 ("close first modal → open second → close second → first reopens"). That issue was closed in 2024, but this still happens on 5.2.14. It is not the same bug as #2765 (that one is a modal that is still animating closed, with the default enableDismissOnClose) and it is not fixed by #2698: I applied #2698 alone and A still reopens.
Root cause. With enableDismissOnClose={false}, a closed modal stays mounted and stays in the provider's queue with status CLOSED. When the next modal is presented, the provider (switch behaviour) calls minimize() on it. In handleMinimize, currentIndexRef.current === -1 is read as "minimized before it finished its mounting animation", so the index prop is saved as the restore point and the status becomes MINIMIZING. When the next modal is dismissed, the provider calls restore(), which runs snapToIndex(index), and the modal the user closed opens.
Instrumented trace from a release build (modal A = bottom-sheet-modal-2, B = bottom-sheet-modal-4):
onChange A to=-1 (A closed → status CLOSED)
mountSheet B, switch, queue=[A]
minimize A status=2 (CLOSED) cur=-1 → restoreIndex = index prop (0), status = MINIMIZING
handleWillUnmountSheet B
restore A status=4 (MINIMIZING) restoreIndex=0
onChange A to=0 ← A opens again
Suggested fix (PR to follow): in handleMinimize, treat a CLOSED modal as having nothing to minimize, and make restore() a no-op when there is no restore index. A modal minimized while open is not affected.
Results on a real device with the Snack below:
| Library | Closed A, then B shown and dismissed | Open A, then B shown and dismissed |
|---|---|---|
| 5.2.14 | A reopens ✗ | A is not restored ✗ (#2788 / #2685) |
| 5.2.14 + suggested fix | A stays closed ✓ | A is not restored (unchanged) |
| 5.2.14 + #2698 | A reopens ✗ | A is restored ✓ |
| 5.2.14 + #2698 + suggested fix | A stays closed ✓ | A is restored ✓ |
Reproduction steps
- Modal A:
enableDismissOnClose={false},snapPoints={['40%']}, defaultstackBehavior(switch). - Modal B: defaults,
snapPoints={['25%']}. - Tap 1. Present A: A opens.
- Tap 2. Close A: A closes (
onChange -1). - Tap 3. Present B: B opens.
- Tap 4. Dismiss B: B closes, and A opens again (
A onChange 0), with no user action on A.
Expected: A stays closed.
Reproduction sample
https://snack.expo.dev/a2g2T79TyuloUkrflGrG9
Relevant log output
> 1. Present A
A onChange 0
> 2. Close A
A onChange -1
> 3. Present B
B onChange 0
> 4. Dismiss B
A onChange 0
B onChange -1
Activity