반응형
모달창을 구현하는 과정에서 효율성을 높이고 재사용할 수 있게 커스텀 훅을 활용해 모달 기능을 구현했습니다.
왜 커스텀 훅으로 모달을 구현했는지? 📖
모달창을 구현하다 보니 다양한 컴포넌트에서 동일한 모달 로직을 반복적으로 사용하는 문제가 있었습니다.
그래서 효율성과 재사용성을 높일 수 있게 커스텀 훅으로 분리했습니다!
useModal 커스텀 훅 📖
`useModal` 훅에서는 모달의 상태, 열기, 닫기, 포탈 렌더링 로직을 포함하고 있고 외부로 반환하고 있습니다.
import { useCallback, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
const useModal = () => {
const [isOpen, setIsOpen] = useState(false);
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
useEffect(() => {
if (typeof window !== 'undefined') {
setPortalElement(document.getElementById('overlays'));
}
}, [isOpen]);
const openModal = useCallback(() => setIsOpen(true), []);
const closeModal = useCallback(() => setIsOpen(false), []);
const Modal = ({ children }: { children: React.ReactNode }) => {
if (!isOpen || !portalElement) return null;
return createPortal(
<div
onClick={closeModal}
style={{ zIndex: 999, backgroundColor: 'rgba(53, 53, 53, 0.6)' }}
className="fixed inset-0"
>
{children}
</div>,
portalElement
);
};
return { isOpen, openModal, closeModal, Modal };
};
export default useModal;
모달을 열고 닫는 기능을 `useCallback`을 통해 메모이제이션하여 불필요한 렌더링을 방지했습니다.
const openModal = useCallback(() => setIsOpen(true), []);
const closeModal = useCallback(() => setIsOpen(false), []);
모달창이 렌더링 될 DOM 요소입니다. 최상단에 `overlays` ID를 가진 DOM 노드를 찾아 상태로 저장합니다.
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
useEffect(() => {
if (typeof window !== 'undefined') {
setPortalElement(document.getElementById('overlays'));
}
}, [isOpen]);
`createPortal`을 사용해 모달을 렌더링하고 `isOpen`과 `portalElement`가 존재할 때만 렌더링 할 수 있도록 조건문을 설정했습니다.
const Modal = ({ children }: { children: React.ReactNode }) => {
if (!isOpen || !portalElement) return null;
return createPortal(
<div
onClick={closeModal}
style={{ zIndex: 999, backgroundColor: 'rgba(53, 53, 53, 0.6)' }}
className="fixed inset-0"
>
{children}
</div>,
portalElement
);
};
useModal 모달창 구현 📖
KakaoMap.tsx
`KakaoMap`컴포넌트에서 `useModal`훅을 호출해 마커를 클릭할 때 모달을 열고 `StampModal` 컴포넌트에서 `Modal`컴포넌트를 렌더링 합니다.
// KakaoMap.tsx
const KakaoMap = () => {
const { openModal, Modal } = useModal();
return (
<>
<KakaoMapMarker openModal={openModal} />
<StampModal Modal={Modal} />
</>
);
};
export default KakaoMap;
StampModal.tsx
`Modal` 컴포넌트를 감싸 모달에 들어갈 내용을 작성했습니다.
// StampModal.tsx
import Image from 'next/image';
import Link from 'next/link';
import Icon from '../Icons/Icon';
interface StampModalPropsType {
Modal: ({ children }: { children: React.ReactNode }) => React.ReactPortal | null;
}
const StampModal = ({ Modal }: StampModalPropsType) => {
return (
<Modal>
<div
onClick={(e) => e.stopPropagation()}
className="absolute bottom-0 left-0 z-[1000] flex h-[342px] w-full animate-slideDownModal flex-col items-center justify-center rounded-tl-[32px] rounded-tr-[32px] bg-white p-6 shadow-overlayShadow"
>
{...}
</div>
</Modal>
);
};
export default StampModal;
결과 화면 📖
회고 🧐
커스텀 훅을 이용한 모달 상태 관리와 포탈 렌더링을 분리해 사용해서 재사용성과 가독성을 높일 수 있었습니다.
덕분에 프로젝트 복잡성을 줄이고 일관성을 유지할 수 있었습니다!
반응형
'공부 > Next' 카테고리의 다른 글
[성능 최적화] Next.js HydrationBoundary를 활용한 서버-클라이언트 데이터 최적화 (0) | 2024.11.16 |
---|---|
Supabase에서 타입 설정하기 (0) | 2024.11.11 |
[리팩토링] queries 폴더 구조 변경 및 메서드명 수정 (1) | 2024.11.07 |
[리팩토링] Icon 컴포넌트 리팩토링 (0) | 2024.11.02 |
[리팩토링] 데이터 로직 분리 및 서버 상태 관리 최적화 (0) | 2024.10.28 |
Next 카카오맵을 활용한 시도별 가로 스크롤 버튼 이동 구현 (0) | 2024.10.28 |
[리팩토링] Next 카카오 맵 폴리곤 렌더링 리팩토링 - 커스텀 훅, 유틸 함수 구조 개선 (0) | 2024.10.24 |
Next 카카오 맵 행정구역 나누기 (1) | 2024.10.23 |