
요구사항
- 페이지 진입 시 팝업창이 떠야 한다.
ㄴ 다른 DOM 요소에 영향을 안 주기 위해 리액트 포탈로 모달 형식으로 뜨게 하자.
- 팝업창 닫기 버튼을 추가해야 한다.
ㄴ 포탈 닫기
- 팝업창 내 이미지 노출한다. (이미지는 수시로 변경될 것으로 추측)
ㄴ 팝업창 컴포넌트에 이미지를 props로 받기
- 오늘 하루 그만보기 기능을 추가한다.
ㄴ 만료기한이 있는 쿠키를 사용하자 (1일 기한)
구현결과
1️⃣ 리액트 포탈로 모달 만들기
<body>
<div id="root"></div>
{}
<div id="modal-root"></div>
<script
type="module"
src="/src/main.tsx"></script>
</body>
import { ReactNode } from 'react';
import ReactDOM from 'react-dom';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
onUnSeeToday: () => void;
children: ReactNode;
}
const Modal = (props: ModalProps) => {
const { isOpen, onClose, onUnSeeToday, children } = props;
if (!isOpen) return null;
return ReactDOM.createPortal(
<div
onClick={onClose}
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div
onClick={e => e.stopPropagation()}
className="relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg">
{}
{children}
{}
<div className="absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4">
<button
onClick={onUnSeeToday}
className="text-[14px] text-gray-500">
하루동안 보지 않기
</button>
<button
onClick={onClose}
className="text-[14px] text-gray-500">
닫기
</button>
</div>
</div>
</div>,
document.getElementById('modal-root')!
);
};
export default Modal;
import Modal from '@/components/modal';
import { useEffect, useState } from 'react';
const MainPage = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const handleModalClose = () => {
setIsModalOpen(false);
};
useEffect(() => {
setIsModalOpen(true);
}, []);
return (
<>
<div>메인 페이지</div>
<Modal
isOpen={isModalOpen}
onClose={handleModalClose}>
<img
src="https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg"
alt="팝업창 이미지"
className="h-full w-full object-contain"
/>
</Modal>
</>
);
};
export default MainPage;
2️⃣ 쿠키로 하루동안 보지 않기 기능 만들기
import Modal from '@/components/modal';
import { useEffect, useState } from 'react';
import { useCookies } from 'react-cookie';
const MainPage = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const handleModalClose = () => {
setIsModalOpen(false);
};
const [cookies, setCookie] = useCookies(['POPUP_TODAY']);
const handleUnSeeToday = () => {
setCookie('POPUP_TODAY', 'true', {
maxAge: 60 * 60 * 24
});
setIsModalOpen(false);
};
useEffect(() => {
if (cookies.POPUP_TODAY) return;
setIsModalOpen(true);
}, []);
return (
<>
<div>메인 페이지</div>
<Modal
isOpen={isModalOpen}
onClose={handleModalClose}
onUnSeeToday={handleUnSeeToday}>
<img
src="https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg"
alt="팝업창 이미지"
className="h-full w-full object-contain"
/>
</Modal>
</>
);
};
export default MainPage;
import { ReactNode } from 'react';
import ReactDOM from 'react-dom';
interface PopupModalProps {
isOpen: boolean;
onClose: () => void;
onUnSeeToday?: () => void;
children: ReactNode;
}
const PopupModal = (props: ModalProps) => {
const { isOpen, onClose, onUnSeeToday, children } = props;
if (!isOpen) return null;
return ReactDOM.createPortal(
<div
onClick={onClose}
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div
onClick={e => e.stopPropagation()}
className="relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg">
{}
{children}
{}
<div className="absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4">
{}
<button
onClick={onUnSeeToday}
className="text-[14px] text-gray-500">
하루동안 보지 않기
</button>
<button
onClick={onClose}
className="text-[14px]
text-gray-500">
닫기
</button>
</div>
</div>
</div>,
document.getElementById('modal-root')!
);
};
export default PopupModal;

배운 점
- 모달은 많이 다뤄봐서 단순한 UI 정도 수준까지 느껴지고, 하루동안 팝업 보지 않기 등과 같은 쿠키를 이용한 컴포넌트는 처음 구현해보는데 조건에 따라 처리만 해준다면 그렇게 어려운 로직은 아닌 것 같다고 느꼈다.
- 이번에는 가독성이 그렇게 좋지 않은 걸 봐서는 추상화가 덜 되었던 것 같다.
- 지금 보니 하루동안 보지 않기 등을 추상화를 하면 가독성과 책임 분리가 더 잘 될 것 같다.