ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 모달 바깥 부분 클릭시 모달 꺼지게 하기
    React 2022. 4. 12. 23:29

    저는 다양한 프로젝트를 하며 모달 기능을 구현해보았는데요. 모달 기능을 구현하면서 모달 바깥쪽을 클릭 시 모달이 꺼지게 하는 기능을 구현해야 하는 상황이 생길 때가 많았습니다. 처음에 이 기능을 구현할 때 어려움을 겪었던 것이 생각나 한 번 글로써 다시 정리해보려 합니다!

     

    이번화는 다음 스택을 사용하였습니다.

     

    - Next.js

    - Typescript

    - Styled-Component

    - Recoil

     

    아래의 코드들은 저의 한 프로젝트에서 복사해온 코드입니다.

     

     

     

     


     

     

     

     

    일단 Recoil을 사용하여 전역 변수를 선언을 해주었습니다.

    export const isOpen = atom<boolean>({
      key: "isOpen",
      default: false,
    });
    
    export const isWriteModalOpen = atom<boolean>({
      key: "isWriteModalOpen",
      default: false,
    });

     

    그리고 버튼을 클릭 시 모달이 나오게끔 모달 컴포넌트를 만들어보겠습니다.

     

    WriteModal.tsx

    import React from "react";
    import { useRecoilState } from "recoil";
    import { isWriteModalOpen } from "../../../atoms/index";
    import Input from "../../Input/Modal/Input";
    import * as s from "./Style";
    import TextInput from "../../Input/TextInput/TextInput";
    import ModalButton from "../../Button/ModalButton/ModalButton";
    
    const people = [
      "선택",
      "1명",
      "2명",
      "3명",
      "4명",
      "5명",
      "6명",
      "7명",
      "8명",
      "10명",
      "11명",
      "12명",
      "13명",
      "14명",
      "15명",
      "16명",
      "17명",
      "18명",
      "19명",
      "20명",
    ];
    
    type modal = {
      visible: boolean;
    };
    
    interface ModalProps {
      modalObj: modal;
    }
    
    const WriteModal: React.FC<ModalProps> = ({ modalObj }) => {
      const [writemodalIsClose, setWriteModalIsClose] =
        useRecoilState(isWriteModalOpen);
    
      function onClose() {
        setWriteModalIsClose(false);
      }
    
      return (
        <>
          {!writemodalIsClose ? null : (
            <>
              <s.ModalOverlay visible={modalObj.visible} onClick={onClose} />
              <s.ModalWrapper visible={modalObj.visible}>
                <Input type="text" />
                <s.Dropdown>
                  <p>인원수</p>
                  <s.DropdownSelect>
                    {people.map((people, idx) => (
                      <s.DropdownOption key={idx}>{people}</s.DropdownOption>
                    ))}
                  </s.DropdownSelect>
                </s.Dropdown>
                <TextInput />
                <s.ButtonWrapper>
                  <ModalButton text="취소" btnType={"cancel"} onClick={onClose} />
                  <ModalButton text="땡겨!!" btnType={"pull"} onClick={onClose} />
                </s.ButtonWrapper>
              </s.ModalWrapper>
            </>
          )}
        </>
      );
    };
    
    export default WriteModal;

     

    Style.ts

    import styled from "styled-components";
    import Config from "../../../Constants/Config.json";
    
    type StyleProps = {
      visible?: boolean;
      mode?: string;
    };
    
    export const ModalOverlay = styled.div<StyleProps>`
      display: ${(props) => (props.visible ? "block" : "none")};
      z-index: 999;
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      background-color: ${Config.COLOR.SUPERLIGHTGRAY};
      opacity: 0.4;
    `;
    
    export const ModalWrapper = styled.div<StyleProps>`
      width: 45%;
      height: 80%;
      display: ${(props) => (props.visible ? "block" : "none")};
      z-index: 1000;
      position: fixed;
      top: 10%;
      left: 28%;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      padding: 2rem;
      background-color: ${({ theme }) => theme.background};
      border-radius: 10px;
    `;
    
    export const Dropdown = styled.div`
      display: flex;
      margin-top: 1.3vh;
      position: relative;
    
      p {
        font-size: 18px;
        font-weight: bold;
        color: #989898;
      }
    
      &:hover + .lists {
        opacity: 1;
        visibility: visible;
      }
    `;
    
    export const DropdownSelect = styled.select`
      display: flex;
      justify-content: center;
      align-items: center;
      margin-top: -0.1vh;
    
      margin-left: 0.5%;
      width: 10%;
      height: 2rem;
      border-radius: 3px;
      background-color: ${Config.COLOR.BLUE};
      color: ${Config.COLOR.WHITE};
      font-size: 18px;
      font-weight: bold;
    
      .icon {
        cursor: pointer;
      }
    `;
    export const DropdownOption = styled.option`
      padding: 1rem;
      margin-left: 2vh;
      color: ${Config.COLOR.WHITE};
      font-size: 18px;
      font-weight: bold;
    `;
    
    export const ButtonWrapper = styled.div`
      width: 100%;
      height: 6%;
      margin-left: 65.3%;
      margin-top: -4vh;
    `;

     

     

     


     

     

    코드 설명

    1. WriteModal.tsx라는 모달 컴포넌트를 하나를 만들고 그 안에 ModalOverlay 스타일을 이용하여 visible 값을 관리해줘요.
    (여기서 값을 관리할 때 visible type을 선언해주고 Style.ts에서 StyleProps로 visible props 값을 가져와 아래의 코드처럼 값을 비교해줘요)

    type StyleProps = {
      visible?: boolean;
    };
    
    export const ModalOverlay = styled.div<StyleProps>`
      display: ${(props) => (props.visible ? "block" : "none")};
      z-index: 999;
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      background-color: ${Config.COLOR.SUPERLIGHTGRAY};
      opacity: 0.4;
    `;

    2. 해당 ModalOverlay로 모달 바깥 부분의 부분을 지정해줘요. 그리고 ModalOverlay, ModalWrapper에 visible 값을 넣어줍니다.

              <s.ModalOverlay visible={modalObj.visible} onClick={onClose} />
              <s.ModalWrapper visible={modalObj.visible}>

     

    3. ModalOverlay에 onClick 함수를 선언해주어 바깥쪽을 클릭 시 모달이 꺼지게끔 하는 함수를 선언해줍니다. 여기서 전역 변수를 사용하여 값을 관리해줍니다.

      const [writemodalIsClose, setWriteModalIsClose] =
        useRecoilState(isWriteModalOpen);
    
      function onClose() {
        setWriteModalIsClose(false);
      }

     

     

    마무리

    이런식으로 StyleProps와 useRecoilState를 사용하여 모달 바깥쪽 클릭 시 꺼지게끔 하는 기능을 구현해보았는데요.
    코드를 수정할 부분이나 피드백을 주시면 감사히 받겠습니다 😎

    댓글

Made by JeongTaehwan