Open thebiltheory opened 2 years ago
@thebiltheory: hello! :wave:
This issue is being automatically closed because it does not follow the issue template.
Hello,
cc @gorhom
For some reason, the screen heights are unable to be calculated by onLayout
.
I managed to make this "work" with a dirty hack but not really dynamically.
It does the job for now.
Inside the file where lies your <BottomSheet>
// Will serve to hold each screens height.
const [onLayoutHeight, setOnLayoutHeight] = useState(null);
// Bottom Sheet dynamic content code
const initialSnapPoints = useMemo(() => ["CONTENT_HEIGHT"], []);
const {
animatedHandleHeight,
animatedSnapPoints,
animatedContentHeight,
handleContentLayout,
} = useBottomSheetDynamicSnapPoints(initialSnapPoints);
// Custom handle
const dynamicContentLayoutHandle = (event) => {
// Sets the "new height" every time this function is rendered
setOnLayoutHeight(event.nativeEvent.layout.height);
// Bottom Sheet `onLayout` Handle, which needs
// { nativeEvent: { layout: { height: <YOUR SCREEN HEIGHT> } } }
handleContentLayout(event);
};
return (
<BottomSheetModal
name="Select Title"
ref={newBookingModal.ref}
onDismiss={resetCarForm}
keyboardBehavior="interactive"
snapPoints={animatedSnapPoints}
handleHeight={animatedHandleHeight}
contentHeight={animatedContentHeight}
enablePanDownToClose={true}
keyboardBlurBehavior="restore"
android_keyboardInputMode="adjustPan"
>
<NavigationContainer independent={true}>
<BottomSheetView
style={{
flex: 1,
// Set the new height every time it changes
height: onLayoutHeight,
backgroundColor: "green",
}}
onLayout={dynamicContentLayoutHandle}
>
// Pass your custom `onLayout` handle to your @react/navigation navigator
<NewBookingNavigator onLayout={dynamicContentLayoutHandle} />
</BottomSheetView>
</NavigationContainer>
</BottomSheetModal>
);
});
Inside your navigator, on your Stack.Screen
<Stack.Screen
name="ScreenName"
component={SelectVehiculeScreen}
// 🤢 pass your layout function as initial param
// I know, it doesn't feel right
initialParams={{ onLayout }}
/>
Inside your screens
// Use react navigation's `useOnFocus` to trigger
// the `onLayout` function you have passed to the navigator
useFocusEffect(() => {
route.params?.onLayout({ nativeEvent: { layout: { height: 750 } } });
});
try this
import React, { useCallback, useMemo, useRef } from "react";
import { StyleSheet, Text, View, Button } from "react-native";
import BottomSheet, {
BottomSheetDraggableView,
useBottomSheetDynamicSnapPoints,
} from "@gorhom/bottom-sheet";
import {
NavigationContainer,
useFocusEffect,
useNavigation,
} from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { useAnimatedStyle } from "react-native-reanimated";
const Stack = createStackNavigator();
const ScreenA = ({ animatedContentHeight }) => {
const { navigate } = useNavigation();
const isFocus = useRef(false);
const contentHeight = useRef(0);
const handleContentLayout = useCallback(
({
nativeEvent: {
layout: { height },
},
}) => {
if (isFocus.current) {
contentHeight.current = height;
animatedContentHeight.value = height;
}
},
[animatedContentHeight]
);
useFocusEffect(() => {
isFocus.current = true;
animatedContentHeight.value = contentHeight.current;
return () => {
isFocus.current = false;
};
});
return (
<View onLayout={handleContentLayout}>
<View style={[{ height: 400 }, styles.dummyScreen]}>
<Text>Screen A</Text>
<Button
title="navigate to Screen B"
onPress={() => navigate("ScreenB")}
/>
</View>
</View>
);
};
const ScreenB = ({ animatedContentHeight }) => {
const isFocus = useRef(false);
const contentHeight = useRef(0);
const handleContentLayout = useCallback(
({
nativeEvent: {
layout: { height },
},
}) => {
if (isFocus.current) {
contentHeight.current = height;
animatedContentHeight.value = height;
}
},
[animatedContentHeight]
);
useFocusEffect(() => {
isFocus.current = true;
animatedContentHeight.value = contentHeight.current;
return () => {
isFocus.current = false;
};
});
return (
<View onLayout={handleContentLayout}>
<View style={[{ height: 200 }, styles.dummyScreen]}>
<Text>Screen B</Text>
</View>
</View>
);
};
const App = () => {
const initialSnapPoints = useMemo(() => [100, "CONTENT_HEIGHT"], []);
const {
animatedContentHeight,
animatedHandleHeight,
animatedSnapPoints,
handleContentLayout,
} = useBottomSheetDynamicSnapPoints(initialSnapPoints);
const sheetContentAnimatedStyle = useAnimatedStyle(
() => ({
minHeight:
animatedContentHeight.value === 0 ? 200 : animatedContentHeight.value,
}),
[animatedContentHeight]
);
return (
<View style={styles.container}>
<BottomSheet
handleHeight={animatedHandleHeight}
contentHeight={animatedContentHeight}
snapPoints={animatedSnapPoints}
>
<BottomSheetDraggableView style={sheetContentAnimatedStyle}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
cardStyle: { backgroundColor: "white" },
}}
>
<Stack.Screen name="ScreenA">
{() => (
<ScreenA animatedContentHeight={animatedContentHeight} />
)}
</Stack.Screen>
<Stack.Screen name="ScreenB">
{() => (
<ScreenB animatedContentHeight={animatedContentHeight} />
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
</BottomSheetDraggableView>
</BottomSheet>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#333",
},
sheetContent: {
minHeight: 1,
},
dummyScreen: {
alignItems: "center",
justifyContent: "center",
},
});
export default App;
@thebiltheory does the example above fixed your issue ?
@thebiltheory does the example above fixed your issue ?
Nope. But I've improved my version of my implementation until better approach.
I will give a second shot to yours in a couple of days.
For the ones coming after me, a very straight forward example for this exists in the docs: https://gorhom.github.io/react-native-bottom-sheet/hooks#usebottomsheetdynamicsnappoints
The solution of @thebiltheory just sets a fixed height for the child screen of 750
in useFocusEffect
.
I had to figure out how to also have a variable height for that, and ended up with an additional dirty hack.
Somehow it only works when I call onLayout
inside of a ScrollView
which is actually not scrolling.
Also, to support the SafeArea you can add a paddingBottom of insets.bottom (and using 20 here if the device doesn't have the SafeArea).
Maybe this is useful for someone here.
interface Props {
route: RouteProp<BottomScreenParams, 'ExampleChildScreen'>
}
export default function ExampleChildScreen(props: Props) {
const onLayout = props.route.params.onLayout
const insets = useSafeAreaInsets()
return (
<ScrollView scrollEnabled={false}>
<View onLayout={onLayout} style={{ paddingBottom: Math.max(insets.bottom, 20) }}>
<Text>Test</Text>
</View>
</ScrollView>
)
}
EDIT: for multiple screens, useFocusEffect
is needed. I updated my code like this:
interface Props {
route: RouteProp<BottomScreenParams, 'ExampleChildScreen'>
}
export default function ExampleChildScreen(props: Props) {
const onLayout = props.route.params.onLayout
const insets = useSafeAreaInsets()
const [layout, setLayout] = useState<LayoutChangeEvent>()
useFocusEffect(useCallback(() => {
if (layout && onLayout) {
onLayout(layout)
}
}, [layout]))
return (
<ScrollView scrollEnabled={false}>
<View
onLayout={(event) => {
event.persist()
setLayout(event)
}}
style={{ paddingBottom: Math.max(insets.bottom, 20) }}
>
<Text>Test</Text>
</View>
</ScrollView>
)
}
try this
import React, { useCallback, useMemo, useRef } from "react"; import { StyleSheet, Text, View, Button } from "react-native"; import BottomSheet, { BottomSheetDraggableView, useBottomSheetDynamicSnapPoints, } from "@gorhom/bottom-sheet"; import { NavigationContainer, useFocusEffect, useNavigation, } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import { useAnimatedStyle } from "react-native-reanimated"; const Stack = createStackNavigator(); const ScreenA = ({ animatedContentHeight }) => { const { navigate } = useNavigation(); const isFocus = useRef(false); const contentHeight = useRef(0); const handleContentLayout = useCallback( ({ nativeEvent: { layout: { height }, }, }) => { if (isFocus.current) { contentHeight.current = height; animatedContentHeight.value = height; } }, [animatedContentHeight] ); useFocusEffect(() => { isFocus.current = true; animatedContentHeight.value = contentHeight.current; return () => { isFocus.current = false; }; }); return ( <View onLayout={handleContentLayout}> <View style={[{ height: 400 }, styles.dummyScreen]}> <Text>Screen A</Text> <Button title="navigate to Screen B" onPress={() => navigate("ScreenB")} /> </View> </View> ); }; const ScreenB = ({ animatedContentHeight }) => { const isFocus = useRef(false); const contentHeight = useRef(0); const handleContentLayout = useCallback( ({ nativeEvent: { layout: { height }, }, }) => { if (isFocus.current) { contentHeight.current = height; animatedContentHeight.value = height; } }, [animatedContentHeight] ); useFocusEffect(() => { isFocus.current = true; animatedContentHeight.value = contentHeight.current; return () => { isFocus.current = false; }; }); return ( <View onLayout={handleContentLayout}> <View style={[{ height: 200 }, styles.dummyScreen]}> <Text>Screen B</Text> </View> </View> ); }; const App = () => { const initialSnapPoints = useMemo(() => [100, "CONTENT_HEIGHT"], []); const { animatedContentHeight, animatedHandleHeight, animatedSnapPoints, handleContentLayout, } = useBottomSheetDynamicSnapPoints(initialSnapPoints); const sheetContentAnimatedStyle = useAnimatedStyle( () => ({ minHeight: animatedContentHeight.value === 0 ? 200 : animatedContentHeight.value, }), [animatedContentHeight] ); return ( <View style={styles.container}> <BottomSheet handleHeight={animatedHandleHeight} contentHeight={animatedContentHeight} snapPoints={animatedSnapPoints} > <BottomSheetDraggableView style={sheetContentAnimatedStyle}> <NavigationContainer> <Stack.Navigator screenOptions={{ headerShown: false, cardStyle: { backgroundColor: "white" }, }} > <Stack.Screen name="ScreenA"> {() => ( <ScreenA animatedContentHeight={animatedContentHeight} /> )} </Stack.Screen> <Stack.Screen name="ScreenB"> {() => ( <ScreenB animatedContentHeight={animatedContentHeight} /> )} </Stack.Screen> </Stack.Navigator> </NavigationContainer> </BottomSheetDraggableView> </BottomSheet> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#333", }, sheetContent: { minHeight: 1, }, dummyScreen: { alignItems: "center", justifyContent: "center", }, }); export default App;
We need to add navigation header height otherwise it will only calculate content
import { useHeaderHeight } from '@react-navigation/elements';
const headerHeight = useHeaderHeight();
const handleContentLayout = useCallback(
({
nativeEvent: {
layout: { height },
},
}) => {
if (isFocus.current) {
contentHeight.current = height + headerHeight; <--
animatedContentHeight.value = height + headerHeight ; <--
}
},
[animatedContentHeight]
);
Bug
Environment info
Steps To Reproduce
Example Snack: https://snack.expo.dev/@thebiltheory/bottom-sheet-v4-with-react-navigation
Describe what you expected to happen:
Reproducible sample code
Example Snack: https://snack.expo.dev/@thebiltheory/bottom-sheet-v4-with-react-navigation
NewBookingNavigator.ts
Originally posted by @thebiltheory in https://github.com/gorhom/react-native-bottom-sheet/discussions/427#discussioncomment-1158471