react-native-bottom-sheet: Modal reopens while dismissing if it gets re-rendered while closing
Bug
This is a bit more precise version (with reproducible code sample) of #191.
If your handler call dismiss
but also triggers the modal to get updated, then modal doesn’t get dismissed, it reopens.
https://user-images.githubusercontent.com/6768840/104815091-e1482c00-5855-11eb-9797-048efd23f69e.mov
Full repro is noted below (thought it only contains 1 line diff from the example app), and related screen code is as below.
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Button from '../../components/button';
import withModalProvider from '../withModalProvider';
const DynamicSnapPointExample = () => {
// state
const [count, setCount] = useState(0);
const [contentHeight, setContentHeight] = useState(0);
// hooks
const bottomSheetRef = useRef<BottomSheetModal>(null);
const { bottom: safeBottomArea } = useSafeAreaInsets();
// variables
const snapPoints = useMemo(() => [contentHeight], [contentHeight]);
// callbacks
const handleIncreaseContentPress = useCallback(() => {
setCount(state => state + 1);
}, []);
const handleDecreaseContentPress = useCallback(() => {
setCount(state => Math.max(state - 1, 0));
}, []);
const handlePresentPress = useCallback(() => {
bottomSheetRef.current?.present();
}, []);
const handleDismissPress = useCallback(() => {
// NOTE: This setCount call triggers the issue
setCount(state => state + 1);
bottomSheetRef.current?.dismiss();
}, []);
const handleOnLayout = useCallback(
({
nativeEvent: {
layout: { height },
},
}) => {
setContentHeight(height);
},
[]
);
// styles
const contentContainerStyle = useMemo(
() => ({
...styles.contentContainerStyle,
paddingBottom: safeBottomArea,
}),
[safeBottomArea]
);
const emojiContainerStyle = useMemo(
() => ({
...styles.emojiContainer,
height: 50 * count,
}),
[count]
);
// renders
const renderBackground = useCallback(
() => <View style={styles.background} />,
[]
);
return (
<View style={styles.container}>
<Button
label="Present"
style={styles.buttonContainer}
onPress={handlePresentPress}
/>
<Button
label="Dismiss"
style={styles.buttonContainer}
onPress={handleDismissPress}
/>
<BottomSheetModal
ref={bottomSheetRef}
index={0}
snapPoints={snapPoints}
backgroundComponent={renderBackground}
>
<BottomSheetView
style={contentContainerStyle}
onLayout={handleOnLayout}
>
<Text style={styles.message}>
Could this sheet modal resize to its content height ?
</Text>
<View style={emojiContainerStyle}>
<Text style={styles.emoji}>😍</Text>
</View>
<Button
label="Yes"
style={styles.buttonContainer}
onPress={handleIncreaseContentPress}
/>
<Button
label="Maybe"
style={styles.buttonContainer}
onPress={handleDecreaseContentPress}
/>
</BottomSheetView>
</BottomSheetModal>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
background: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'white',
},
buttonContainer: {
marginBottom: 6,
},
contentContainerStyle: {
paddingTop: 12,
paddingHorizontal: 24,
backgroundColor: 'white',
},
message: {
fontSize: 24,
fontWeight: '600',
marginBottom: 12,
},
emoji: {
fontSize: 156,
textAlign: 'center',
alignSelf: 'center',
},
emojiContainer: {
overflow: 'hidden',
justifyContent: 'center',
},
});
export default withModalProvider(DynamicSnapPointExample);
Environment info
Library | Version |
---|---|
@gorhom/bottom-sheet | 2.0.4 |
react-native | 0.63.4 |
react-native-reanimated | 1.13.2 |
react-native-gesture-handler | 1.9.0 |
Steps To Reproduce
- Clone the repo below
- Run the example app and select
MODAL > Dynamic Snap Point
example - Press
Present
- Press
Dismiss
Describe what you expected to happen:
- Modal closes without reopening
Reproducible sample code
https://github.com/heejongahn/react-native-bottom-sheet
You can verify this behavior with example/src/sceens/modal/DynamicSnapPointExample.tsx
. (diff from original example)
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 22 (5 by maintainers)
As always… I found the issue not long after posting this comment… I really did not think this would fix my issue, but it did.
From https://github.com/gorhom/react-native-bottom-sheet/pull/205#issuecomment-763074453
Wrapping my easing function in a memo worked:
From:
To:
Same problem here using tab bar, when I navigate from the tab that have the bottom sheet (and its closed) and then comeback to the same tab, the bottom sheet appears open. The problem is only with Android.
FYI This issue still exists in v2.3.0.
Is it really fixed for everyone? Happening to me on v2.3.0.
ref?.current?.dismiss(); button.onPress()
Where onPress leads to rerender of BottomSheet children, causes modal to get stuck in some minimized but not unmounted state.ref?.current?.dismiss(); setTimeout(() => {button.onPress()}, 1000)
On the other hand works perfectly, because rerender definetly occurs after modal was dismissed.Too bad, that dismiss() doesn’t return a promise, then we could call critical methods, when promise was resolved.
this should be fixed with
v2.0.7
🎉For those interested and or still having issues, I found a fix for this.
The example provided in the documentation has the index prop equal to 1, so in the case the bottom sheet is unmounted, then it will default back to 1 (even if you specify snapToIndex or snapToPosition on mount using useEffect, which it will ignore to prioritize the default index).
To fix this we need to have the index prop equal to the last index value before it was unmounted, but using state hooks will result in a re-render every index change. This is what you do instead:
Now the index is tracked without re-rendering each update and if the bottom sheet is unmounted, it will reference the last known index.
Hope this helps! 😊
- Logan (a.k.a. DarkComet)
Massive thanks to you!
Something similar happens in 3.2.1 but when opening the bottom sheet immediately before or after setting any state that would trigger a re-render of the content inside the bottom sheet causes it to close back down mid-animation. A workaround I had to do was doing
setTimeout(() => bottomSheetRef.current.snapTo(1), 200)
which isn’t idealthanks @heejongahn for submitting this issue, i will look into it shortly