파괴적인 동작을 할 때 사용할 confirm modal 만들기
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
interface ConfirmModalProps {
isModalOpen: boolean;
content: string;
handleSubmit: VoidFunction;
handleClose: VoidFunction;
}
const ConfirmModal = ({
isModalOpen,
content,
handleSubmit,
handleClose,
}: ConfirmModalProps) => {
if (!isModalOpen) return null;
const ref = React.useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (ref.current && !ref.current.contains(target)) {
handleClose();
}
};
document.addEventListener("click", handleClickOutside, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
}, []);
return ReactDOM.createPortal(
<div className="fixed bg-black bg-opacity-60 z-50 p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full">
<div
ref={ref}
className="relative mx-auto border border-solid border-gray-200 rounded-lg w-full h-full max-w-md md:h-auto"
>
<div className="relative bg-white rounded-lg shadows">
<button
type="button"
className="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center"
>
<div onClick={handleClose} className="w-6 h-6">
<Xmark />
</div>
<span className="sr-only">Close modal</span>
</button>
<div className="p-6 text-center">
<AlertIcon />
<h3 className="mb-5 text-lg font-normal text-gray-500">
{content}
</h3>
<div className="flex gap-2 justify-center">
<Button onClick={handleSubmit} style_type="alert" type="button">
확인
</Button>
<Button
onClick={handleClose}
style_type="secondary"
type="button"
>
취소
</Button>
</div>
</div>
</div>
</div>
</div>,
document.body
);
};
export default ConfirmModal;
디자인은 단순한 tailwind 템플릿을 사용했다.
중요한 부분은 Portal이었는데 주로 modal 컴포넌트에서 사용되며 쉽게 이해하면 다른 dom 노드에 하위요소로 추가 시켜줄 수 있다.
위 코드에서는 body 태그의 하위컴포넌트로 추가하게 되며 실제 렌더링 되는지 확인해보면 다음과 같이 렌더링 된다
이후 아래와 같이 불러온다.
<>
<li onClick={setTodoOpen} className="py-4 px-5">
<div className="relative px-0 py-2 flex justify-between items-center w-full text-base text-gray-800 text-left bg-white border-0 rounded-none focus:outline-none">
{title}
<TodoMenu
handleUpdateMode={handleUpdateMode}
handleModalOpen={handleModalOpen}
/>
</div>
<AnimatePresence mode="wait">
{isTodoOpen() ? (
<motion.div {...openAnimation}>{content}</motion.div>
) : null}
</AnimatePresence>
</li>
<ConfirmModal
content="TODO를 삭제하시겠습니까?"
isModalOpen={isModalOpen}
handleSubmit={handleDeleteClick}
handleClose={handleModalClose}
/>
</>
문제가 있다면 이렇게 붙여도 될까? 하는 부분이다. 처음 코드를 볼 때 Todo 컴포넌튼데 왠 modal이 붙어있고 상태로 관리되고 있다.
만약 view와 logic으로 분리된다면 조금 더 명확하게 구분할 수 있지 않을까? 생각이 들어 분리하려고 한다.
회고
오늘 공부한 걸 다 적으려니 코드적인 부분이 많았고 기능적으로 정확하게 글로 정리할 수 없을거 같았다.
그래서 간단하라도 적는게 좋을거 같아 짧게라도 글을 작성했다.
'TIL > 트러블슈팅' 카테고리의 다른 글
NextJS auth hoc 구현 (0) | 2023.01.21 |
---|---|
Already included file name differs from file name only in casing... 이슈 (0) | 2023.01.18 |
redux 오픈소스 코드를 뜯어서 createStore 직접 구현해보기 (0) | 2023.01.16 |
공식문서를 통한 비로그인 접근 제어하기 (0) | 2023.01.14 |
typescript interface property 변환하기, react-query mutate 파라미터 오류 해결, common 컴포넌트로 분리하기 (0) | 2023.01.12 |