React useModal hook 만들기

서나무·2022년 12월 15일
4

React

목록 보기
3/5
post-thumbnail

모달 컴포넌트를 만들어서 사용하는데, 모달의 렌더링 여부가 결정되는 state를 모달 컴포넌트에게 위임할 수 없을까하는 생각이 들었다.

기존 코드 예시

import { useState } from "react";
import Modal from "../components/modal/modal";

export default function App() {
  const [isOpen, setIsOpen] = useState(false);
  const open = () => {
    setIsOpen(() => true);
  };
  const close = () => {
    setIsOpen(() => false);
  };
  return (
    <div>
      <button onClick={open}>open modal</button>
      <Modal open={isOpen}>
        <p>삭제하시겠습니까?</p>
        <div>
          <button onClick={close}>OK</button>
          <button onClick={close}>Cancle</button>
        </div>
      </Modal>
    </div>
  )
}

Modal 컴포넌트는 props로 받은 isOpen이 true일 경우에만 모달을 보여주고, false인 경우 null을 반환해서 아무것도 렌더링 되지 않도록 했다.

이렇게하면 모달을 사용하는 모든 화면 혹은 컴포넌트에서 모달의 상태 값을 가지고 있어야 하는데, 이 부분이 너무 효율적이지 않았다.

이럴 때는 hook을 만들어서 사용하자!

먼저 모달 컴포넌트를 수정했다.

기존에는 props로 isOpen을 받아서 렌더링 여부를 모달 컴포넌트 내부에서 결정했지만, 이 일을 hook에게 위임할 예정이다.

// css
const modalWrapStyle = {
  width: '100%', 
  height: '100vh', 
  overflow: 'hidden', 
  position: 'fixed', 
  top: 0, left: 0, 
  backgroundColor: 'rgba(0,0,0,0.3)',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  textAlign: 'center'
}

const modalStyle = {
  width: 380, 
  padding: 20, 
  backgroundColor: '#fff'
}

// 어떠한 작업도 하지 않고 모달을 보여주는 역할만 함
export default function Modal({ onClose, children }) {
  return (
    <div 
      className="modal"
      style={{...modalWrapStyle}}
      onClick={onClose}
    >
      <div 
        className="modal_container"
        style={{...modalStyle}}
        onClick={(e) => e.stopPropagation()}
      >
        {children}
      </div>
    </div>
  )
}

class명이 modal인 div는 모달의 외부 배경이다.

배경을 클릭하면 모달이 닫히도록 할 것이기 때문에 onClick 이벤트 발생시 hook에서 받아온 onClose 함수를 호출해서 모달을 닫는다.

class명이 modal_container인 div가 모달이다.

modal_container를 클릭하면 부모에게 이벤트가 전달되기 때문에 onClick={(e) => e.stopPropagation()}로 이벤트가 전달되는 것을 막았다.

useModal hook

모달의 상태 값을 가지며 제어할 수 있는 useModal hook이다.

import React, { useCallback, useState } from 'react';
import Modal from '../components/modal/modal';

// `useBlur` props로 모달 외부를 클릭하면 모달을 닫을지 선택하도록 했다.
const useModal = ({ useBlur = true } = {}) => {
  // 모달의 렌더링 여부를 설정할 상태 값
  const [isOpen, setIsOpen] = useState(false);

  // 모달 열기
  const open = useCallback(() => {
    setIsOpen(() => true);
  }, []);

  // 모달 닫기
  const close = useCallback(() => {
    setIsOpen(() => false);
  }, []);

  // isOpen이 true라면 Modal 컴포넌트를 반환, false라면 null을 반환
  return {
    Modal: isOpen
      ? ({ children }) => (
          <Modal onClose={useBlur ? close : null}>{children}</Modal>
        )
      : () => null,
    open,
    close,
    isOpen,
  };
};

export default useModal;

return 할 때 자체적으로 isOpen을 체크해서 true일 경우에만 Modal 컴포넌트를 반환해주고, false인 경우에는 null을 반환하도록 한다.

useModal 사용하기

useModal hook을 사용하면 이렇게 간단하게 모달 컴포넌트를 사용할 수 있게 된다.

import useModal from '../hooks/useModal';

export default function App() {
  const { Modal, open, close } = useModal();

  return (
    <div>
      <button onClick={open}>open modal</button>
      <Modal>
        <p>삭제하시겠습니까?</p>
        <div>
          <button onClick={close}>OK</button>
          <button onClick={close}>Cancle</button>
        </div>
      </Modal>
    </div>
  )
}

isOpen으로 모달이 보여지는지 체크할 필요가 없어졌으며, Modal 컴포넌트는 isOpen이 true일 때만 보여지는 것을 보장받는다.

profile
주니어 프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2023년 1월 4일

안녕하세요 좋은 글 잘 봤습니다.
글 내용 중 궁금한 점이 있어 질문 드립니다!

css를 style prop으로 전달하실 때 spread 연산자를 사용하지 않고
style={modalStyle}
이런 방식으로 하는 것이 더 간결하다고 생각하는데 spread 연산자를 사용하신 이유가 있으신가요?

1개의 답글