시작하기
빈 React 앱에서 시작해, 사용자의 선택을 모달을 연 코드로 돌려주는 모달까지 만들어 봅니다. 5분 정도 걸립니다.
요구 사항
React Snappy Modal은 React 외에 런타임 의존성이 없습니다. 패키지는 React 16.12, 17, 18을 peer dependency로 선언하며, 이 문서 사이트는 React 19에서 사용하고 있습니다.
모달은 SnappyModal.show()로 열고 Promise를 완료해 닫으므로 async와 await에 익숙하면 좋습니다. TypeScript 타입 선언이 패키지에 포함되어 있어 별도의 @types 패키지가 필요 없습니다.
1. 패키지 설치
사용하는 패키지 매니저로 설치합니다. 스타일시트가 패키지에 포함되어 있고 react-snappy-modal을 import하면 자동으로 적용되므로 따로 추가할 CSS 파일이 없습니다.
1npm install react-snappy-modal
2# or
3yarn add react-snappy-modal
4# or
5pnpm add react-snappy-modal2. SnappyModalProvider 추가
SnappyModalProvider는 열린 모달이 렌더링되는 위치입니다. 앱의 루트 근처에 한 번만 배치하세요. 모달은 Provider의 children 다음에 렌더링되므로 페이지 위에 표시됩니다.
그다음부터는 컴포넌트, 이벤트 핸들러, React 밖의 일반 유틸 함수 등 어디서든 SnappyModal.show()를 호출할 수 있습니다.
1import { createRoot } from 'react-dom/client';
2import { SnappyModalProvider } from 'react-snappy-modal';
3import App from './App';
4
5createRoot(document.getElementById('root')!).render(
6 <SnappyModalProvider>
7 <App />
8 </SnappyModalProvider>,
9);3. 모달 컴포넌트 작성
모달은 평범한 React 컴포넌트입니다. 컴포넌트 안에서 useCurrentModal()을 호출해 resolveModal을 얻습니다. resolveModal(value)를 호출하면 바로 이 모달이 닫히고 value가 모달을 연 코드로 전달됩니다.
라이브러리는 콘텐츠 위치와 배경 딤만 처리하고, 콘텐츠에 배경색·여백·테두리를 넣지 않습니다. 아래 예제처럼 카드 스타일은 직접 지정하세요.
1import { useCurrentModal } from 'react-snappy-modal';
2
3export function ChooseColor() {
4 const { resolveModal } = useCurrentModal();
5
6 return (
7 <div style={{ background: 'white', padding: 24, borderRadius: 12 }}>
8 <h2>Pick a color</h2>
9 <button onClick={() => resolveModal('red')}>Red</button>
10 <button onClick={() => resolveModal('blue')}>Blue</button>
11 <button onClick={() => resolveModal(null)}>Cancel</button>
12 </div>
13 );
14}4. 모달을 열고 결과 기다리기
SnappyModal.show(element, options?)는 요소를 렌더링하고 Promise를 반환합니다. 함수는 모달이 닫힐 때까지 await에서 멈췄다가 결과를 받아 이어서 실행됩니다. isOpen 상태나 콜백 prop을 연결할 필요가 없습니다.
1import { useState } from 'react';
2import SnappyModal from 'react-snappy-modal';
3import { ChooseColor } from './ChooseColor';
4
5export default function App() {
6 const [color, setColor] = useState('none');
7
8 const pickColor = async () => {
9 const result = await SnappyModal.show(<ChooseColor />);
10 // Runs only after the modal is closed
11 if (result) setColor(result);
12 };
13
14 return <button onClick={pickColor}>Selected: {color}</button>;
15}Promise가 반환하는 값
결과는 모달이 닫힌 방식에 따라 달라집니다.
| 모달이 닫힌 방식 | 결과 |
|---|---|
resolveModal(value) 또는 SnappyModal.close(value) | value로 resolve |
배경 클릭 (allowOutsideClick이 기본값 true일 때) | undefined로 resolve |
rejectModal(error) 또는 SnappyModal.throw(error) | error로 reject되므로 try/catch로 처리 |
동작 원리
내부적으로 한 번의 호출은 네 단계를 거칩니다.
show()가 모달을 내부 스택에 추가하고 고유한modalId를 부여한 뒤 대기 중인 Promise를 반환합니다.SnappyModalProvider가 다시 렌더링되며 스택의 모든 모달을layer순서대로 그립니다.- 컴포넌트가
resolveModal()을 호출하면 모달이 스택에서 제거되고 Promise가 완료됩니다. await다음 코드가 결과를 받아 실행됩니다.
문제 해결
설정 문제는 대부분 아래 경우 중 하나입니다.
| 증상 | 원인과 해결 |
|---|---|
아무것도 표시되지 않고 await 다음 코드가 실행되지 않음 | SnappyModalProvider가 마운트되지 않았습니다. 화면에 있는 트리를 Provider가 감싸고 있는지 확인하세요. 모달이 닫힐 때까지 Promise는 대기 상태로 남습니다. |
| Error: useCurrentModal must be used within CurrentModalProvider | useCurrentModal()은 SnappyModal.show()로 렌더링된 컴포넌트 안에서만 동작합니다. 훅을 모달 컴포넌트 안으로 옮기거나, 바깥에서는 SnappyModal.close(value)로 닫으세요. |
| 모달 콘텐츠가 투명함 | 콘텐츠 래퍼는 위치만 담당합니다. 루트 요소에 배경과 여백을 지정하거나 옵션으로 className 또는 style을 전달하세요. |
| 모달이 sticky 헤더 아래에 표시됨 | 모달은 기본적으로 z-index: 1000을 사용합니다. 더 큰 zIndex 옵션을 전달하거나 다른 요소의 z-index를 낮추세요. |
| 모달 뒤 페이지가 스크롤되지 않음 | allowScroll은 기본값이 false라서 모달이 열려 있는 동안 페이지를 고정하고, 닫힐 때 스크롤 위치를 복원합니다. 페이지를 가리지 않는 패널이라면 allowScroll: true를 전달하세요. |