[React] 모달창 구현하기

이창준·2024년 3월 3일


최근에 프로젝트를 진행하면서 React로 모달창을 구현하는 일을 하였기에, 이를 잊지 않으려고 끄적끄적 적게 되었다.

들어가기에 앞서, 모달창이 무엇인지에 대해 먼저 알아봐야겠다.


모달(Modal)

모달은 사용자가 특정 버튼이나 링크 등을 클릭했을 때 나오는 창으로, 이전 페이지 위에 새로운 레이어를 만들어 추가적인 정보나 문구르 보여줄 때 사용한다.

여기서 모달과 팝업(Pop-up)을 헷갈릴 수 있는데, 팝업은 현재 열려있는 페이지(브라우저)위에 또 다른 페이지를 띄우는 것을 말한다.

개인적인 생각으로는, 1번 페이지(브라우저)와 연관된 정보를 보여주는 것은 모달로, 개별의 정보를 제공할 때는 팝업을 사용하는 것이 좋다고 생각한다.


React로 모달 구현

import React, { useState } from "react"

...
  
const [showEditOrDeleteModal, setShowEditOrDeleteModal] = useState(false);

...

<Button
	onClick={() => setShowEditOrDeleteModal(!showEditOrDeleteModal)}
>
	그룹 정보
</Button>

{showEditOrDeleteModal && (
<EditOrDeleteModal
	showEditOrDeleteModal={showEditOrDeleteModal}
    setShowEditOrDeleteModal={setShowEditOrDeleteModal}
    groupId={id}
    />
)}

버튼을 클릭했을 때 set함수를 통해 모달을 띄울지 말지 결정하는 변수의 값을 변경한다.
이 boolean 변수를 통해 조건부렌더링을 하게 되는데, showEditOrDeleteModal 가 true이면, 기존에 만들어 주었던 EditOrDeleteModal 컴포넌트를 화면에 나타낸다.

현재 여기서는 조건부 렌더링을 결정하는 변수와 그 set함수를 props로 넘기고 있는데,
다시 생각해보니 set함수만 보내서 컴포넌트 내에서 false로 값만 설정해주면 될 것 같다.


모달 style

화면 중앙에 띄울 모달 디자인을 해야 하는데, 다음은 전체 코드를 올려놓으려고 한다. 여기서 중요한 것은, position을 fixed나 absolute로 주어 고정시켜놓고, z-index를 설정하여 본문과 겹치지 않게 만들어야 한다는 것이다.

모달 컴포넌트

import React, { useState } from "react";
import * as S from "./style";
import DeleteCheckModal from "./DeleteCheckModal";
import EditListModal from "./EditListModal";

function EditOrDeleteModal({
  showEditOrDeleteModal,
  setShowEditOrDeleteModal,
  groupId,
}) {
  const [showDeleteCheckModal, setShowDeleteCheckModal] = useState(false);
  const [showEditListModal, setShowEditListModal] = useState(false);
  const handleButtonClick = () => {
    setShowDeleteCheckModal(true);
  };
  return (
    <S.ModalContainer>
      <S.Exit
        alt="exit"
        onClick={() => setShowEditOrDeleteModal(!showEditOrDeleteModal)}
      />
      <S.TextContainer>
        <S.Text>
          <S.ColorSpan>수정</S.ColorSpan> 또는 <S.ColorSpan>삭제</S.ColorSpan>
          하실 수 있습니다.
        </S.Text>
      </S.TextContainer>
      <S.ButtonContainer>
        <S.Button onClick={handleButtonClick}>그룹 삭제</S.Button>
        <S.Button onClick={() => setShowEditListModal(!showEditListModal)}>
          명단 수정
        </S.Button>
      </S.ButtonContainer>
      {showDeleteCheckModal && (
        <DeleteCheckModal
          setShowDeleteCheckModal={setShowDeleteCheckModal}
          setShowEditOrDeleteModal={setShowEditOrDeleteModal}
          groupId={groupId}
        />
      )}
      {showEditListModal && (
        <EditListModal
          showEditListModal={showEditListModal}
          setShowEditListModal={setShowEditListModal}
          groupId={groupId}
          grade={grade}
          classNum={classNum}
        />
      )}
    </S.ModalContainer>
  );
}

export default EditOrDeleteModal;

style.js

import styled from "styled-components";

export const ModalContainer = styled.div`
  width: 480px;
  height: 288px;
  flex-shrink: 0;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0px 0px 24px 0px rgba(38, 40, 43, 0.15);

  position: fixed;
  z-index: 1000;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);

  display: flex;
  flex-direction: column;
`;

export const Exit = styled.img.attrs({ src: exit })`
  margin-top: 24px;
  margin-right: 24px;
  margin-left: 432px;
  width: 24px;
`;

export const TextContainer = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 9rem;
`;

export const Text = styled.p`
  font-size: 20px;
  font-weight: 700;
  line-height: 32px;
`;

export const ColorSpan = styled.span`
  color: #4849ff;
`;

export const ButtonContainer = styled.div`
  display: flex;
  width: 100%;
  justify-content: space-around;

  // 내부 폰트
  color: #ffffff;
  font-size: 20px;
  font-weight: 700;
`;

export const Button = styled.button`
  width: 192px;
  height: 56px;
  background-color: #4849ff;
`;


(그룹 정보 버튼 클릭)


구글링을 해보니 useRef를 통해서 모달 창 밖을 클릭했을 때 모달을 닫게 하는 기능도 있는 것 같다. 이건 다시 공부하여 기능을 추가한 뒤 새로 포스팅하도록 해야겠다.

profile
개발자가 되고싶은 먼지입니다.

0개의 댓글