hooks를 사용하여 customModal 만들기

김석진·2023년 3월 22일
4

개발을 하던 도중.. 기본 confirm이 너무 맘에 들지 않았던 디자이너는
나에게 custom confirm을 디자인해서 주게 된다

디자인은 사진과 같이 간단한 confirm이었고 한 페이지에만 쓰이는 게 아닌 여러 페이지에서 쓰이는 방식의 confirm 이었다

우선 어디서든 열리게 했어야 했고 title, desc, button text 등 props로 넘겨주어 유동적이게 만들어야 했다

custom modal 만들기

그럼 바로 만들어보자

우선 modal을 만들어보자

Modal.tsx

import { MouseEventHandler, ReactNode } from 'react';
import MiniButton from 'components/common/MiniButton';
import * as S from './style';

type ModalOptionType = 'CONFIRM' | 'ALERT';

interface PropsType {
  option: ModalOptionType;
  title: string;
  content: ReactNode;
  closeText: string;
  confirmText: string;
  handleClose: MouseEventHandler<HTMLButtonElement>;
  handleConfirm: MouseEventHandler<HTMLButtonElement>;
}

const Modal = ({
  option,
  title,
  content,
  closeText,
  confirmText,
  handleClose,
  handleConfirm,
}: PropsType) => {
  return (
    <S.BlurBackground>
      {option === 'CONFIRM' ? (
        <S.Confirm>
          <S.ConfirmWrap>
            <S.ConfirmTextBox>
              <S.ConfirmTitle>{title}</S.ConfirmTitle>
              <S.ConfirmContent>{content}</S.ConfirmContent>
            </S.ConfirmTextBox>
            <S.ConfirmButtonBox>
              <MiniButton
                option="UNFILLED"
                value={closeText}
                onClick={handleClose}
              />
              <MiniButton
                option="FILLED"
                value={confirmText}
                onClick={handleConfirm}
              />
            </S.ConfirmButtonBox>
          </S.ConfirmWrap>
        </S.Confirm>
      ) : (
        <S.Alert>
          <S.AlertWrap>
            <S.AlertTextBox>
              <S.AlertTitle>{title}</S.AlertTitle>
              <S.AlertContent>{content}</S.AlertContent>
            </S.AlertTextBox>
            <S.AlertButtonBox>
              <MiniButton
                option="FILLED"
                value="확인"
                onClick={handleConfirm}
              />
            </S.AlertButtonBox>
          </S.AlertWrap>
        </S.Alert>
      )}
    </S.BlurBackground>
  );
};

export default Modal;

나는 모달이 그냥 confirm만 되게 하는 것이 아닌 props로 옵션을 주어서 alert, confirm 두 개를 만들어 원하는 대로 쓸 수 있게 하였다

그럼 modal을 어떤 식으로 불러오게 되는 것일까?

우선 modal이라는 recoil을 만들어보자

modal.atom.ts

import { ReactNode } from 'react';
import { atom } from 'recoil';

export const modalState = atom<ReactNode>({
  key: 'modalState',
  default: null,
});

recoil은 ReactNode를 타입으로 주었다 recoil이 왜 필요한지는 아래서 GlobalModal.tsx를 설명하면서 같이 설명하겠다

import { modalState } from 'atoms/modal.atom';
import { useRecoilValue } from 'recoil';

const GlobalModal = () => {
  const modal = useRecoilValue(modalState);

  const provide = () => {
    if (!modal) return null;
    return modal;
  };

  return <>{provide()}</>;
};

export default GlobalModal;

index.tsx

import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
import GlobalStyled from 'styles/global.style';
import { RecoilRoot } from 'recoil';
import { BrowserRouter } from 'react-router-dom';
import ScrollTop from 'utils/ScrollTop';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import GlobalModal from 'components/common/GlobalModal';
import App from './App';

const queryClient = new QueryClient();
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement,
);

root.render(
  <BrowserRouter>
    <RecoilRoot>
      <QueryClientProvider client={queryClient}>
        <App />
        <GlobalModal />
        <GlobalStyled />
        <ScrollTop />
        <ToastContainer
          autoClose={2000}
          limit={5}
          pauseOnHover={false}
          position="top-right"
          pauseOnFocusLoss={false}
        />
      </QueryClientProvider>
    </RecoilRoot>
  </BrowserRouter>,
);

GlobalModal은 index에 다음과 같이 배치하여 modalState를 불러와 값이 null이면 아무것도 return 하지 않고 만약 값이 들어있다면 modal을 띄우게 될 것이다

하지만 modal을 띄우게 되려면 useModal이 필요하다 사용법은 어떻게 되는 것이고 어떻게 만드는 걸까? 자세하게 알려주겠다

useModal.ts

import { modalState } from 'atoms/modal.atom';
import { ReactNode } from 'react';
import { useRecoilState } from 'recoil';

export const useModal = () => {
  const [modal, setModal] = useRecoilState(modalState);
  const openModal = (m: ReactNode) => {
    setModal(m);
  };
  const closeModal = () => {
    setModal(null);
  };

  return { openModal, closeModal, modal };
};

useModal은 다음과 같다 openModal( modal component 자리 )
만약 modal component 자리에 modal component가 들어오게 되면 그것을
recoil로 저장시킬 것이고 저장이 되면 GlobalModal에서 return 해줘서 화면에 띄워주는 것이다

여기까지 custom modal의 기본 세팅이다 이제 직접 써보자

사용하기

const generateStudents = () => {
    const { admissionYear, numberOfStudents } = generateStudentsData;
    const grade = new Date().getFullYear() + 1 - admissionYear;
    if (new Date().getFullYear() < admissionYear) {
      toast.error('입학년도를 다시 한번 확인해주세요');
      return;
    }
    openModal(
      <Modal
        option="CONFIRM"
        title="학생 아이디 생성"
        content={
          <p>
            {grade}학년 학생 {numberOfStudents}명의 아이디 생성이 맞는지
            <br /> 다시 한번 확인해주세요
          </p>
        }
        closeText="취소"
        confirmText="생성"
        handleClose={closeModal}
        handleConfirm={generate}
      />,
    );
  };

generateStudents를 onclick={generateStudents}와 같이 넣게 된다면
클릭하였을 때 openModal에 component가 들어가게 될 것이고 modal이 이쁘게
사진과 같이 열릴 것이다

custom modal 만들고 디자이너를 만족시키자!

profile
해탈의 경지에 이르자

1개의 댓글

comment-user-thumbnail
2023년 4월 20일

globalModal에 있는 친구는 함수로 빼는것보다 그냥 && 걸어주거나 삼항으로 처리하는게 더 이해하기 쉬울 것 같기도 합니다~ 어떻게 생각하시는지 궁금해요!!!

답글 달기