Bottom sheet modal doesn't open in production
See original GitHub issueBug
Thanks for taking the time to look at this.
In production it seems that the bottom modal sheet flicks and doesn’t open. If you look at my video attached you will see that the modal flickers slightly down the bottom sometimes it opens fully for a brief half second then closes. I’ve checked if the dismiss call is firing on open which it’s not.
Any help would be greatly appreciated.
UPDATE:
I’m now calling present() in a useFocusHook and the modal is still flickering down the bottom. It appears half way up the screen for half a second then flickers and goes away.

Environment info
iOS - Test Flight
| Library | Version |
|---|---|
| @gorhom/bottom-sheet | ^1.4.1 |
| react-native | 0.62.2 |
| react-native-reanimated | ^1.9.0 |
| react-native-gesture-handler | ^1.6.1 |
Steps To Reproduce
1.Press on button to trigger present() Describe what you expected to happen: 1.Modal to open
Reproducible sample code
- BottomSheet component - Called in screen
const BottomSheetHomeScreenComponent = ({myRef, listData, renderList, extraData, onSearchButtonPress, searchInput, userReducer, isLoading, navigation}) => {
const snapPoints = useMemo(() => ['75%', '90%'], []);
// This controls what is being displayed whether the user is logged in / if they have created a story
const noData = ({section}) => {
if (section.data.length === 0) {
return (
<View style={{alignSelf: 'center', marginTop: 30}}>
<Text>No stories found for this search</Text>
</View>
)
}
};
// This handles the header and what is being displayed depending if the user is logged in or not.
const handleHeaderDisplayComponent = () => {
return (
<ButtonComponent
title={searchInput === "" ? "Press for more search options" : searchInput}
containerStyle={styles.searchButtonContainerStyle}
buttonStyle={styles.searchButtonStyle}
titleStyle={styles.searchButtonTitleStyle}
onButtonPress={() => navigation.push(SEARCH_SCREEN)}
icon={
<IconComponent
name="search"
type="font-awesome-5"
size={ICON_SIZE.iconSizeSmall}
color={COLOR.limeGreen}
style={{marginRight: 20}}
/>
}
/>
)
};
const handleSectionHeader = (section) => {
return (
!section.data.length ? (
null
) : (
<View style={styles.sectionTitle}>
<Text style = {styles.headerText}>{section.title}</Text>
</View>
)
)
};
const handleBackgroundComponent = () => {
return <FeatureComponent userReducer={userReducer} allCollectionsReducer={extraData} />
};
return (
<BottomSheet
ref={myRef}
initialSnapIndex={0}
snapPoints={snapPoints}
shouldMeasureContentHeight={true}
backgroundComponent={handleBackgroundComponent}
>
<View style={styles.contentContainer}>
{listData[0].data === null || isLoading ? (
<View style={{marginLeft: 10, marginRight: 10}}>
{handleHeaderDisplayComponent()}
<View style={{marginTop: 10}}>
<SkeletonStoryCard />
</View>
</View>
) : (
<BottomSheetSectionList
sections={listData}
renderItem={renderList}
renderSectionHeader={({ section }) => handleSectionHeader(section)}
keyExtractor={(item) => item.id}
extraData={listData.data}
keyboardShouldPersistTaps='always'
stickySectionHeadersEnabled={false}
ListHeaderComponent={handleHeaderDisplayComponent}
renderSectionFooter={noData}
/>
)}
</View>
</BottomSheet>
)
};
- Screen - Parent
const SearchScreen = ({ navigation, userReducer, allCollectionsReducer, addBookMarkedStory, removeBookMarkedStory }) => {
const alertContext = useAlertContext();
const { present, dismiss } = useBottomSheetModal();
// State
const [search, setSearch] = useState("");
const [recentSearchHistory, setRecentSearchHistory] = useState([]);
const [storyResults, setStoryResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
// bottomSheet
const contentSheetRef = null;
// Makes the search full page modal appear
const handlePresentPress = useCallback(() => {
present(
<SearchModalComponent
updateSearch={(search) => updateSearch(search)}
onTagSelect={(tag, id) => (updateSearch(tag.title, id), handleDismissPress())}
onSubmit={(search) => (saveRecentSearchToStorage(search), updateSearch(search), handleDismissPress())}
onRecentSearchSelect={(recentSearch) => (updateSearch(recentSearch), handleDismissPress())}
search={search}
recentSearchHistory={recentSearchHistory}
handleOnDismiss={() => handleDismissPress()}
/>,
{
snapPoints: ['95%', '95%'],
dismissOnOverlayPress: false,
dismissOnScrollDown: false
},
);
}, [present, search, recentSearchHistory]);
const handleDismissPress = useCallback(() => {
Alert.alert('IT HAS BEEN CALLED')
dismiss();
}, [dismiss]);
const updateSearch = async (search, id) => {
setIsLoading(true);
// Sets the search to update
setSearch(search);
// Calls API
const response = await getSearchResults(search, id);
if (response.status === 200) {
// If no data returned still ensure that the states are empty arrays
setStoryResults(!response.data ? [] : response.data.stories);
return setIsLoading(false);
} else {
console.log('error searching')
return setIsLoading(false);
}
};
// When the user is viewing the screen for the first time this will fire.
const userTutorial = async () => {
const screenTutorial = await getToken(SEARCH_SCREEN_TUTORIAL);
if (screenTutorial === null) {
storeToken(SEARCH_SCREEN_TUTORIAL, 'complete');
// If user is not logged in and users first open of app display this pop-up
return alertContext.alert({
title: "Welcome to the search page!",
body: "You can search for users and stories here",
display: 'modal',
theme: 'modalWelcomeTheme',
iconName: 'search',
iconColor: COLOR.limeGreen,
iconSize: ICON_SIZE.iconSizeXXLarge,
});
} else {
return;
}
};
// Save search to local storage
const saveRecentSearchToStorage = async (search) => {
if (recentSearchHistory.length === 3) {
recentSearchHistory.unshift({text: search});
recentSearchHistory.pop();
} else {
recentSearchHistory.unshift({text: search});
}
await storeToken(RECENT_SEARCH_HISTORY_IDENTIFIER, JSON.stringify(recentSearchHistory))
};
// Recovers recent search history from local storage
const recoverRecentSearchHistory = async () => {
const data = await getToken(RECENT_SEARCH_HISTORY_IDENTIFIER);
setRecentSearchHistory(data ? JSON.parse(data) : []);
};
useFocusEffect(
useCallback(() => {
recoverRecentSearchHistory();
userTutorial();
}, []),
);
// Only sets the search with all stories on first load. If user searches and clears search it will return all stories
useEffect(() => {
setStoryResults(allCollectionsReducer.stories);
}, [allCollectionsReducer.stories])
// When the user clicks on story card
const onStoryPress = (storyID) => {
const userID = userReducer.id
// Navigates to the Screens navigator then storyStack then to view Story
return navigation.navigate(SCREENS_NAVIGATOR, {
screen: STORY_SCREEN_STACK,
params: {
screen: VIEW_STORY_SCREEN,
params: { storyID, userID }
},
});
};
// Handle when user clicks on bookmark button
const onBookmarkPress = async (hasUserBookMarkedStory, storyID) => {
// If user is not logged in
if (!userReducer.id) {
return navigation.navigate(AUTH_NAVIGATOR, {
screen: AUTH_SCREEN_STACK,
params: {
screen: LOGIN_SCREEN
}
});
};
// If already bookmarked
if (hasUserBookMarkedStory) {
const response = await unBookMarkStory(storyID, userReducer.id);
response.status === 200 ? removeBookMarkedStory(storyID) : console.log("ERROR");
} else {
const response = await bookMarkStory(storyID, userReducer.id);
response.status === 200 ? addBookMarkedStory(storyID) : console.log("ERROR");
};
};
// Renders story cards
const renderResults = ({item}) => {
const hasUserLikedStory = userReducer.likedStories ? userReducer.likedStories.includes(item.id) : false;
const hasUserBookMarkedStory = userReducer.bookMarks ? userReducer.bookMarks.includes(item.id) : false;
return (
<TouchableOpacity onPress={() => onStoryPress(item.id)} style={styles.card}>
<StoryCardComponent
title={item.title}
description={item.description}
tags={item.tags}
avatarURL={item.interviewer.avatarURL}
likes={item.likes}
hasUserLikedStory={hasUserLikedStory}
hasUserBookMarkedStory={hasUserBookMarkedStory}
onBookMarkPress={() => onBookmarkPress(hasUserBookMarkedStory, item.id)}
/>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<BottomSheetSearchComponent
onSearchButtonPress={() => handlePresentPress()}
myRef={contentSheetRef}
listData={[
{title: 'Stories', data: storyResults}
]}
renderList={renderResults}
extraData={allCollectionsReducer}
userReducer={userReducer}
searchInput={search}
isLoading={isLoading}
/>
</View>
);
};
- Modal
const SearchModalComponent = ({recentSearchHistory: _recentSearchHistory, search: _search, updateSearch, onSubmit, onTagSelect, handleOnDismiss, onRecentSearchSelect, storyReducer}) => {
const [search, setSearch] = useState(_search);
const [recentSearchHistory, setRecentSearchHistory] = useState(_recentSearchHistory);
const mockArray = [];
const handleInputChange = useCallback((search) => {
setSearch(search);
return updateSearch(search);
}, [updateSearch]);
// This displays the tags in the search modal
const displayTags = () => {
const tag = storyReducer.allTags.map((tag) => {
return (
<TagComponent
key={tag.id}
tag={tag}
onSelectedTag={(tag) => onTagSelect(tag, tag.id)}
/>
)
});
return tag;
};
const mockLoadingTags = () => {
for (let i = 0; i < 10; i++) {
mockArray.push(i);
};
};
const displayRecentSearch = () => {
const recentSearch = recentSearchHistory.map((recent, index) => {
return (
<View style={styles.recentSearchComponentContainer} key={index}>
<RecentSearchComponent
title={recent.text}
onSelect={(title) => onRecentSearchSelect(title)}
/>
</View>
)
});
return recentSearch;
};
return (
<View style={styles.container}>
<ScrollView style={styles.contentContainer} keyboardShouldPersistTaps={'handled'} showsVerticalScrollIndicator={false}>
<TouchableOpacity onPress={() => handleOnDismiss()} style={styles.masterHeaderContainer}>
<IconComponent
name="chevron-left"
type="font-awesome-5"
size={ICON_SIZE.iconSizeSmall}
color={COLOR.grey}
style={styles.iconStyle}
/>
<Text style={styles.masterHeader}>Search</Text>
</TouchableOpacity>
<TextInputComponent
onChange={(search) => handleInputChange(search)}
onSubmitEditing={(search) => onSubmit(search)}
containerStyle={styles.searchBarContainerStyle}
iconName="search"
iconType="font-awesome"
inputContainerStyle={styles.inputContainerStyle}
placeholder="Search for stories here"
returnKeyType="search"
clearTextOnFocus={true}
enablesReturnKeyAutomatically={true}
/>
<Text style={styles.header}>Search by recent</Text>
<View style={styles.recentSearchContainer}>
{displayRecentSearch()}
</View>
<Text style={styles.header}>Search by tags</Text>
<View style={styles.tagContainer}>
{storyReducer.allTags === null ? (
mockLoadingTags(),
mockArray.map((el, i) => {
return <SkeletonTagComponent key={i} />
})
) : (
displayTags()
)}
</View>
</ScrollView>
</View>
)
};
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (2 by maintainers)
Top Results From Across the Web
Android Bottom Sheet Modal (Dialog) doesn't open completely
I am trying to show a bottom sheet dialog in my app on a button click. But the dialog is opening partially. I...
Read more >How to Create Modal Bottom Sheet in Android | Java - Medium
In this blog post, we're going to create a modal bottom sheet in android. Nowadays most app using this material design concept in...
Read more >Modal & Nonmodal Dialogs: When (& When Not) to Use Them
Modal dialogs interrupt users and demand an action. They are appropriate when user's attention needs to be directed toward important ...
Read more >Most performant modal/bottom sheet library? : r/reactnative
The modal provided by react native themselves doesn't have a backdrop or slide down feature, so that doesn't work. The rest of the...
Read more >Newest 'bottom-sheet' Questions - Stack Overflow
I got an error No default value Have used Bottom Sheet library and used it's components as shown below: BottomSheetModalProvider BottomSheet ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

Hey @gorhom , I’m not the OP, but since I’ve been experiencing this same issue, I’ve created a stripped down version of my app for you to take a look at here. I know its not something that runs from
/example, which works fine on my end, but maybe there is a problem relating with Expo.Quick instructions:
yarn installandexpo start, then press eitherato launch Android orito launch iOS.Here’s two GIFs of what I’m seeing from both platforms:
Android: Working as intended.
iOS: Not working as intended.
Edit: Let me know if this is a different bug, since the setup is a bit different, so that I can open up a new issue.
hi @maxckelly, @MarcusOy I just released Alpha 5, it should fix this issue, please try it and let me know , thanks
also check out the new documents website ( still in progress ) 🎉