Notice
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- Access Key 생성
- react native
- GIT
- 리엑트 네이티브 아이콘
- s3 upload
- 리액트
- 문자열 대소문자 구별
- PongWorld
- babel.config.js
- AWS
- react native font
- 리액트 네이티브 에러
- AWS Access Key
- js
- react native 세팅
- React
- fire base
- Project
- aws bucket 정책
- react native CLI
- 문자열 대소문자
- Next.js
- firebase 라이브러리
- 백준
- 리액트 네이티브
- error
- 에러
- react native picker
- react native 개발
- img upload
Archives
- Today
- Total
밝을희 클태
[ React ] Custom Confirm Modal 만들기 본문
프로젝트에서 기본 Confirm Modal을 사용하고 있었는데 일단 못생기기도 했고 모바일에서 대화창 숨기기랑 항목이 데스크톱과 다르게 있는데 이 항목을 한번 클릭하면 더 이상 Confirm Modal이 뜨지 않는다. 우리 프로젝트에서 Confirm Modal의 입력을 꼭 "예" 를 받아다 다음 상황으로 진행이 되기 때문에 실수로 대화창 숨기기를 클릭해 버리면 사용자는 왜 다음 상황으로 안 넘어가는지 알 수 없는 문제가 발생했다.
그래서 useContext API를 사용해 전역에서 사용가능한 Confirm Modal을 만들어 볼거다.
ModalProvider.jsx
형태 | 용도 | |
modalMessage | String | 모달의 메인 메세지 |
modalSubMessage | String | 모달의 서브 메세지 |
isSelectableModal | boolean | 선택 가능한 모달 여부 |
resolvePromise | function | 'Promise'를 완료 하는 함수 |
'use client';
import { usePathname } from 'next/navigation';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
const ModalContext = createContext();
export const useModal = () => useContext(ModalContext);
export const ModalProvider = ({ children }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalMessage, setModalMessage] = useState('');
const [modalSubMessage, setModalSubMessage] = useState('');
const [isSelectableModal, setIsSelectableModal] = useState(false);
const [size, setSize] = useState('w-72');
const [resolvePromise, setResolvePromise] = useState(null);
const pathName = usePathname();
const openModal = useCallback(({ message, subMessage = '', isSelectable = false, size }) => {
setModalMessage(message);
setModalSubMessage(subMessage);
setIsSelectableModal(isSelectable);
setIsModalOpen(true);
if (size) setSize(size);
return new Promise(resolve => {
setResolvePromise(() => resolve);
});
}, []);
const resetModalState = useCallback(() => {
setModalMessage('');
setModalSubMessage('');
setIsSelectableModal(false);
setSize('w-72');
}, []);
const closeModal = useCallback(() => {
if (resolvePromise) {
resolvePromise(false);
setResolvePromise(null);
}
setIsModalOpen(false);
resetModalState();
}, [resolvePromise]);
const confirmModal = useCallback(() => {
if (resolvePromise) {
resolvePromise(true);
setResolvePromise(null);
}
setIsModalOpen(false);
resetModalState();
}, [resolvePromise]);
useEffect(() => closeModal(), [pathName]);
return (
<ModalContext.Provider
value={{
isModalOpen,
modalMessage,
modalSubMessage,
openModal,
closeModal,
confirmModal,
isSelectableModal,
size,
}}
>
{children}
</ModalContext.Provider>
);
};
Modal.jsx
'use client';
import { useModal } from './ModalProvider';
export default function Modal() {
const { isModalOpen, modalMessage, modalSubMessage, closeModal, confirmModal, isSelectableModal, size } = useModal();
if (!isModalOpen) return null;
return (
<div
className="fixed top-0 left-0 w-screen custom-dvh flex justify-center items-center bg-black bg-opacity-50 z-50"
onClick={e => {
if (e.currentTarget === e.target) closeModal();
}}
>
<div
className={`${size} rounded space-y-4 p-3 bg-white flex flex-col justify-center items-center border-solid border`}
>
<div className="space-y-2">
<p className="font-semibold text-lg text-center break-all whitespace-pre-line">{modalMessage}</p>
{modalSubMessage && (
<p className="text-center text-gray-400 text-sm whitespace-pre-line">{modalSubMessage}</p>
)}
</div>
<div className="flex justify-center space-x-2 h-10 font-semibold">
<button
className={`flex rounded justify-center items-center w-32 ${
isSelectableModal ? 'bg-gray-200' : 'bg-black text-white'
} `}
onClick={closeModal}
>
{isSelectableModal ? '취소' : '확인'}
</button>
{isSelectableModal && (
<button
className="flex rounded justify-center items-center w-32 bg-black text-white"
onClick={confirmModal}
>
확인
</button>
)}
</div>
</div>
</div>
);
}
실제 사용 예시
아래와 같이 ModalProvier와 Modal을 Layout 컴포넌트에 추가해준다.
export default function MainLayout({ children }) {
return (
<ModalProvider>
{children}
<Modal />
</ModalProvider>
);
}
import { useModal } from '@/app/(main)/_components/ModalProvider';
export default function Page() {
const { openModal } = useModal();
const openConfirmModal = async () => {
const res = await openModal({
message: 'hello world?',
isSelectable: true,
});
if (!res) return;
// 추가 작업이 여기에 위치
};
return <button onClick={openConfirmModal}>모달 열기</button>;
}
'KEYNUT 프로젝트' 카테고리의 다른 글
[ Next.JS ] userAgent를 효율적으로 관리해보자 (1) | 2024.09.04 |
---|---|
내가 설정한 description이 안 나온다. (0) | 2024.08.28 |
[ Next.JS ] PWA 설정하기 feat. Please check your GenerateSW plugin configuration:[WebpackGenerateSW] 'images' property is not expected to be here. Did you mean property 'mode'? (0) | 2024.08.19 |
[ AWS ] CloudFront를 활용해 S3 GET요청 최적화 (0) | 2024.08.18 |
[ Next.JS ] 이미지 슬라이더(image-slider) UX 개선 (0) | 2024.08.18 |