
📦 app
 ┣ 📜 _layout.tsx
 ┣ 📜 index.tsx
 📦 components
 ┣ 📂 ui
 ┗ ┗ 📜 CustomBottomSheet.tsx
npm install @gorhom/bottom-sheetyarn add @gorhom/bottom-sheet@^5npx expo install react-native-reanimated react-native-gesture-handlernpm install react-native-reanimated react-native-gesture-handleryarn add react-native-reanimated react-native-gesture-handlernpx expo customize
react-native-reanimated/plugin을 플러그인에 추가합니다.
module.exports = {
	presets: [
		... // don't add it here :)
	],
	plugins: [
		...
		'react-native-reanimated/plugin',
	],
};metro.config.js 파일에서 기존 Metro 구성을 wrapWithReanimatedMetroConfig 함수를 사용하여 래핑합니다.// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config");
const {
  wrapWithReanimatedMetroConfig,
} = require("react-native-reanimated/metro-config");
 
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
 
module.exports = wrapWithReanimatedMetroConfig(config);npx expo start -cnpm start -- --reset-cacheyarn start --reset-cacheimport { GestureHandlerRootView } from 'react-native-gesture-handler';
 
export default function Layout({ children }) {
  return (
    <GestureHandlerRootView>
      <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
        <Stack/>
      </ThemeProvider>
    </GestureHandlerRootView>
  );
}cd ios && pod install && cd ..npx expo prebuildimport React, { useCallback, useRef } from "react";
import { Text, StyleSheet, View, Button } from "react-native";
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
 
const App = () => {
  // ref
  const bottomSheetRef = useRef<BottomSheet>(null);
 
  // callbacks
  const handleSheetChanges = useCallback((index: number) => {
    console.log("handleSheetChanges", index);
  }, []);
 
  // renders
  return (
    <View style={styles.container}>
      <Button title="expand" onPress={() => bottomSheetRef.current?.expand()} />
      <BottomSheet ref={bottomSheetRef} onChange={handleSheetChanges}>
        <BottomSheetView style={styles.contentContainer}>
          <Text>Awesome 🎉</Text>
          <Button
            title="close"
            onPress={() => bottomSheetRef.current?.close()}
          />
        </BottomSheetView>
      </BottomSheet>
    </View>
  );
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
  },
  contentContainer: {
    flex: 1,
    padding: 36,
    alignItems: "center",
  },
});
 
export default App;
| props | 설명 | type | default | required | 
|---|---|---|---|---|
| index | 바텀시트의 초기상태 설정 (0, -1) | number | 0 | NO | 
| snapPoints | 바텀시트의 스냅포인트 설정  숫자 & 문자열 배열로, 고정될 높이를 의미합니다. ex. [200, "60%"] enableDynamicSizing가 false일 경우 필수  | [] | - | YES | 
| animateOnMount | 마운트 시 애니메이션 여부 | boolean | true | NO | 
| backdropComponent | 바텀시트 뒷배경 컴포넌트 | BottomSheetBackdrop | - | NO | 
| enablePanDownToClose | 아래로 드래그하여 닫기 여부 | boolean | true | NO | 
import React from "react";
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
 
interface CustomBottomSheetProps extends BottomSheetProps {
  bottomSheetModalRef: React.RefObject<BottomSheet>;
  children: React.ReactNode;
  snapPoints?: string[];
}
 
const CustomBottomSheet: React.FC<CustomBottomSheetProps> = ({
  bottomSheetModalRef,
  children,
  snapPoints = ["30%"],
  ...props
}) => {
 
  return (
    <BottomSheet
      ref={bottomSheetModalRef}
      index={0}
      snapPoints={snapPoints}
      style={{
        zIndex: 10,
        elevation: 10,
      }}
      enableDynamicSizing={false}
      enablePanDownToClose={false}
      {...props}
    >
      <BottomSheetView style={{ flex: 1 }}>{children}</BottomSheetView>
    </BottomSheet>
  );
};
 
export default CustomBottomSheet;import React, { useRef } from "react";
import { Text, StyleSheet, View, Button } from "react-native";
import BottomSheet from "@gorhom/bottom-sheet";
import CustomBottomSheet from "@/components/ui/CustomBottomSheet";
 
const App = () => {
  // ref
  const bottomSheetRef = useRef<BottomSheet>(null);
 
  // renders
  return (
    <View style={styles.container}>
      <Button title="expand" onPress={() => bottomSheetRef.current?.expand()} />
      <CustomBottomSheet
				children={
          <View style={styles.contentContainer}>
            <Text>Awesome 🎉</Text>
            <Button
              title="close"
              onPress={() => bottomSheetRef.current?.close()}
            />
          </View>
        }
        bottomSheetModalRef={bottomSheetRef}
      />
    </View>
  );
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
  },
  contentContainer: {
    flex: 1,
    padding: 36,
    alignItems: "center",
  },
});
 
export default App;
//...
const CustomBottomSheet: React.FC<BottomSheetProps> = ({
  bottomSheetModalRef,
  children,
  snapPoints = ["30%", "60%"],
  ...props
}) => {
  const renderBackdrop = useCallback(
    (backdropProps: any) => (
      <BottomSheetBackdrop
        {...backdropProps}
        pressBehavior="close"
        appearsOnIndex={0}
        disappearsOnIndex={-1}
      />
    ),
    []
  );
 
  return (
    <BottomSheet
      ref={bottomSheetModalRef}
      index={0}
      snapPoints={snapPoints}
      style={{
        zIndex: 10,
        elevation: 10,
      }}
      backdropComponent={renderBackdrop}
      enableDynamicSizing={false}
      enablePanDownToClose={true}
      {...props}
    >
      <BottomSheetView style={{ flex: 1 }}>{children}</BottomSheetView>
    </BottomSheet>
  );
};
 
export default CustomBottomSheet;
import BottomSheet, {
  BottomSheetView,
  BottomSheetBackdrop,
  BottomSheetProps,
} from '@gorhom/bottom-sheet';
import { BottomSheetDefaultBackdropProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types';
import React, { useCallback } from 'react';
 
interface CustomBottomSheetProps extends BottomSheetProps {
  bottomSheetModalRef: React.RefObject<BottomSheet>;
  children: React.ReactNode;
  snapPoints?: string[];
}
 
const CustomBottomSheet: React.FC<CustomBottomSheetProps> = ({
  bottomSheetModalRef,
  children,
  snapPoints = ['30%'],
  ...props
}) => {
  const renderBackdrop = useCallback(
    (backdropProps: BottomSheetDefaultBackdropProps) => (
      <BottomSheetBackdrop
        {...backdropProps}
        pressBehavior="close"
        appearsOnIndex={0}
        disappearsOnIndex={-1}
      />
    ),
    [],
  );
 
  return (
    <BottomSheet
      ref={bottomSheetModalRef}
      index={0}
      snapPoints={snapPoints}
      style={{
        zIndex: 10,
        elevation: 10,
      }}
      backdropComponent={renderBackdrop}
      enableDynamicSizing={false}
      enablePanDownToClose={true}
      {...props}
    >
      <BottomSheetView style={{ flex: 1 }}>{children}</BottomSheetView>
    </BottomSheet>
  );
};
 
export default CustomBottomSheet;import BottomSheet from '@gorhom/bottom-sheet';
import React, { useRef } from 'react';
import { Text, StyleSheet, View, Button } from 'react-native';
 
import CustomBottomSheet from '@/components/atoms/CustomBottomSheet';
 
const App = () => {
  // ref
  const bottomSheetRef = useRef<BottomSheet>(null);
 
  // renders
  return (
    <View style={styles.container}>
      <Button title="expand" onPress={() => bottomSheetRef.current?.expand()} />
      <CustomBottomSheet
        index={0}
        children={
          <View style={styles.contentContainer}>
            <Text>Awesome 🎉</Text>
            <Button
              title="close"
              onPress={() => bottomSheetRef.current?.close()}
            />
          </View>
        }
        bottomSheetModalRef={bottomSheetRef}
      />
    </View>
  );
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  contentContainer: {
    flex: 1,
    padding: 36,
    alignItems: 'center',
  },
});
 
export default App;