
기존에 react spring bottom sheet를 사용하여 바텀 시트를 구현하려다가 해당 라이브러리가 2년동안 업데이트를 안해서 호환성 문제에 부딪히고 말았다. 그래서 대안으로 @gorhom/bottom-sheet를 설치하려 했지만 해당 라이브러리는 리액트 네이티브 전용이라서 PWA로 구현하고자 했던 내 프로젝트와는 맞지 않아 결국 react-modal-sheet를 선택하게 되었다.
해당 라이브러리를 사용해서 개발하던 도중 한가지 문제에 봉착했다.
그것은 바로 모바일 환경에서 react-modal-sheet를 사용하여 바텀시트를 구현했을 때는 문제가 없었지만, PC 환경에서는 바텀시트가 제대로 작동하지 않는 문제가 있었다.

내가 이상하게 구현했나 싶어 공식 홈페이지의 데모사이트를 참고해보아도 모바일 환경에서는 잘되었지만 PC 환경에서는 바텀시트가 올라오지 않는 경우가 많았다.
그래서 어떻게 해야되나 생각하는 도중 한가지 데모만이 PC환경에서도 잘 작동되는 것이 확인되었다.
잘 작동되는 데모는 snap-points인데 왜 이것만 잘 작동하는 지 코드를 확인해 보았다.
기존 작성했던 코드
import { useEffect, useRef, useState } from 'react';
import { Sheet, type SheetRef } from 'react-modal-sheet';
import { SHEET_SNAP_POINTS, SHEET_INITIAL_SNAP } from '@constants/sheet';
import { BottomSheetContent } from '@components/common/BottomSheet/CustomBottomSheet.style';
interface BottomSheetProps {
children: React.ReactNode;
isOpen?: boolean;
onClose: () => void;
}
const CustomBottomSheet = ({ children, isOpen = false, onClose }: BottomSheetProps) => {
const ref = useRef<SheetRef>();
const [snapPoint, setSnapPoint] = useState(SHEET_INITIAL_SNAP);
return (
<Sheet
ref={ref}
isOpen={isOpen}
onClose={onClose}
onSnap={(snapIndex) => console.log('> Current snap point index:', snapIndex)}
snapPoints={SHEET_SNAP_POINTS}
initialSnap={SHEET_INITIAL_SNAP}
>
<Sheet.Container>
<Sheet.Header />
<BottomSheetContent disableDrag={true} style={{ paddingBottom: ref.current?.y }}>
{children}
</BottomSheetContent>
</Sheet.Container>
<Sheet.Backdrop />
</Sheet>
);
};
export default CustomBottomSheet;
snap-points 코드
import { useState, useRef, useEffect } from 'react';
import { styled } from 'styled-components';
import { Sheet, type SheetRef } from 'react-modal-sheet';
import { Button } from './common';
import { useMetaThemeColor } from './hooks';
const snapPoints = [-50, 0.5, 200, 0];
const initialSnap = 1; // Initial snap point when sheet is opened
export function SnapPoints() {
const ref = useRef<SheetRef>();
const [isOpen, setOpen] = useState(false);
const [snapPoint, setSnapPoint] = useState(initialSnap);
const snapTo = (i: number) => ref.current?.snapTo(i);
const open = () => setOpen(true);
const close = () => setOpen(false);
useEffect(() => {
console.log('> Current snap point is', snapPoint);
}, [snapPoint]);
useMetaThemeColor({
when: isOpen,
from: '#fff',
to: '#000',
});
return (
<>
<Button onClick={open}>Bottom Sheet with Snap Points</Button>
<Sheet
ref={ref}
isOpen={isOpen}
onClose={close}
onSnap={setSnapPoint}
snapPoints={snapPoints}
initialSnap={initialSnap}
rootId="root"
>
<Sheet.Container>
<Sheet.Content>
<SheetContentWrapper>
<Controls>
<Button onClick={() => snapTo(0)}>
Snap to -50 (from top)
</Button>
<Button onClick={() => snapTo(1)}>Snap to 50%</Button>
<Button onClick={() => snapTo(2)}>Snap to 200</Button>
<Button onClick={() => snapTo(3)}>Snap to 0 (close)</Button>
</Controls>
</SheetContentWrapper>
</Sheet.Content>
</Sheet.Container>
<Sheet.Backdrop onTap={close} />
</Sheet>
</>
);
}
const SheetContentWrapper = styled.div`
height: 100%;
display: flex;
flex-direction: column;
padding: 16px;
`;
const Controls = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;
겉보기에는 별 차이가 없어 보였지만 onSnap함수에서 한가지 차이점이 있었다. 그것은 바로 기존 코드의 onSnap함수는 스냅 포인트가 변경될 때 콘솔 로그만 출력하며 상태를 업데이트하지 않는 다는 점이다.
<Sheet
ref={ref}
isOpen={isOpen}
onClose={onClose}
onSnap={(snapIndex) => console.log('> Current snap point index:', snapIndex)}
snapPoints={SHEET_SNAP_POINTS}
initialSnap={SHEET_INITIAL_SNAP}
>
snap-points의 코드를 살펴보면 onSnap을 통해 스냅 포인트를 상태로 관리하고 있는 것을 확인할 수 있다.
const [snapPoint, setSnapPoint] = useState(initialSnap);
<Sheet
ref={ref}
isOpen={isOpen}
onClose={close}
onSnap={setSnapPoint}
snapPoints={snapPoints}
initialSnap={initialSnap}
rootId="root"
>
해당 코드를 기반으로 기존에 작성했던 코드를 수정하였더니
import { useRef, useState } from 'react';
import { Sheet, type SheetRef } from 'react-modal-sheet';
import { SHEET_SNAP_POINTS, SHEET_INITIAL_SNAP } from '@constants/sheet';
import { BottomSheetContent } from '@components/common/BottomSheet/CustomBottomSheet.style';
interface BottomSheetProps {
children: React.ReactNode;
isOpen?: boolean;
onClose: () => void;
}
const CustomBottomSheet = ({ children, isOpen = false, onClose }: BottomSheetProps) => {
const ref = useRef<SheetRef>();
const [_, setSnapPoint] = useState<number>(SHEET_INITIAL_SNAP);
return (
<Sheet
ref={ref}
isOpen={isOpen}
onClose={onClose}
onSnap={setSnapPoint}
snapPoints={SHEET_SNAP_POINTS}
initialSnap={SHEET_INITIAL_SNAP}
>
<Sheet.Container>
<Sheet.Header />
<BottomSheetContent disableDrag={true} style={{ paddingBottom: ref.current?.y }}>
{children}
</BottomSheetContent>
</Sheet.Container>
<Sheet.Backdrop />
</Sheet>
);
};
export default CustomBottomSheet;

정상적으로 바텀시트가 스냅 포인트를 반영하며 잘 작동하는 것을 확인할 수 있었다.
추후에 배포 환경에서도 문제없이 동작하는지 확인해볼 예정이다.