코딩하는라민

[React] useModal Hook 여러 개의 모달 관리하기 본문

Core/React

[React] useModal Hook 여러 개의 모달 관리하기

코딩하는라민 2024. 5. 2. 20:52
728x90
반응형

[React] useModal Hook 여러 개의 모달 관리하기

my project modal layout

 

useModal Hook 이란?

`useModal` hook 은 React 에서 모달을 간편하게 관리하기 위한 방법이다.

useModal hook 을 통해 열기, 닫기 상태와 같은 모달과 관련된 로직을 모듈화해서 재사용할 수 있다.

 

왜 커스텀 훅을 만들어서 사용할까?

모달을 사용할 때 모달을 열고 닫기 위해 `isOpen` 라는 state 를 사용한다.

하지만 여러 페이지에서 모달을 사용할 경우 각 컴포넌트마다 isOpen 을 매번 선언해줘야한다.

import { useState } from "react"
import LoginModal from "@/components/modalsLoginModal"

const Home = () => {
  const [isOpen, setIsOpen] = useState(false)
  
  const openModal = () => setIsOpen(true)
  const closeModal = () => setIsOpen(false)
 
  return (
    <>
      <button onClick={openModal}>로그인</button>
      {isOpen && (
        <LoginModal closeModal={closeModal} />
      )}
    </>
  )
}

 

하지만 `useModal` 을 사용하면 컴포넌트마다 isOpen 을 선언해줄 필요가 없어진다.

따라서 모달을 사용하는 과정을 단순화하고 모달 로직을 `재사용 가능`한 형태로 추상화하여 코드의 가독성과 유지보수성을 향상시킬 수 있다.

 

하지만 모달 로직이 단순하거나 재사용률이 떨어진다면 직접 구현하는 편이 더 간편할 수 있다.

하지만 useModal 사용으로 인해 코드의 재사용성이 증가하고, 모달 관련 로직을 관리하기 편하다.

 

useModal Hook 사용방법

useModal Hook 기본 형태

useModal 에 모달을 열고 닫기 위한 openModal 함수, CloseModal 함수와 모달 컴포넌트르를 조건부 렌더링하기 위한 isOpen 을 정의해주면 된다.

import { useState } from 'react'

const useModal = () => {
  const [isOpen, setIsOpen] = useState(false)

  const openModal = () => setIsOpen(true)
  const closeModal = () => setIsOpen(false)

  return {
    isOpen,
    openModal,
    closeModal
  };
};

 

사용하기
import useModal from '@/components/modal/useModal.ts'

const Components = () => {
  const { isOpen, openModal, closeModal } = useModal();

  return (
    <div>
      <button onClick={openModal}>열기</button>
      {isOpen && (
        <div>
          <div>모달입니다.</div>
          <button onClick={closeModal}>닫기</button>
        </div>
      )}
    </div>
  );
};

 

하지만 이러한 형태는 한 컴포넌트 내에서 다른 여러 모달을 열 경우 사용이 불가능하다.

useModal hook 을 한 컴포넌트 내에서 여러 개의 모달을 다룰 수 있게 커스텀해보자.

 

여러 개의 모달을 관리하는 useModal hook

여러 모달을 관리하는 modals state 를 생성해준다. modals 의 타입은 key 가 string 타입이고 value 가 boolean 인 객체 형태이다.

모달을 열고 닫을 때마다 modals 을 업데이트 해주는데, 기존의 값에서 새로운 값만 업데이트하는 형식이다. 이 때, open, close 함수의 매개변수로 key 값으로 모달의 이름을 string 형태로 넣어주면 된다.

import { useState } from 'react'

interface Modals {
  [key: string]: boolean
}

const useModal = () => {
  const [modals, setModals] = useState<Modals>({})

  const openModal = (key: string) => {
    setModals(prev => ({
      ...prev,
      [key]: true
    }))
  }

  const closeModal = (key: string) => {
    setModals(prev => ({
      ...prev,
      [key]: false
    }))
  }

  const isOpen = (key: string) => modals[key]

  return {
    isOpen,
    openModal,
    closeModal
  }
}

export default useModal

 

사용하기

기존에는 isOpen 을 state 의 형태로 open, close 함수에서 값을 업데이트 했으나, modals 의 key 값에 맞는 모달 상태를 가져와 사용할 모달 컴포넌트를 조건부 렌더링해주면 된다.

사용 방법은 위의 경우와 거의 유사하다.

import useModal from '@/hook/useModal'
import CategoryModal from '@/components/modals/CategoryModal'
import LoginModal from '@/components/modals/LoginModal'

const Home = () => {
  const { isOpen, openModal, closeModal } = useModal()
  return (
    <>
      {
        isOpen('카테고리') && (
          <CategoryModal closeModal={closeModal} />
        )
      }
      <button onClick={openModal('카테고리')}>카테고리 열기</button>
      {
        isOpen('로그인') && (
          <LoginModal closeModal={closeModal} />
        )
      }
    <button onClick={openModal('로그인')}>로그인</button>
    </>
  )  	
}

 

실행하고자 하는 모달 컴포넌트에 closeModal 을 props 로 내려주고, 모달 컴포넌트에서 closeModal 을 실행하면 된다.

// CategoryModal.tsx
const CategoryModal = ({
  closeModal
}: {
  closeModal: (key: string) => void
}) => {
	...
  return (
    <ModalLayout closeModal={() => closeModal('카테고리')}>
      <Container>
        ...
      </Container>
    </ModalLayout>
  )
}
// ModalLayout.tsx
const ModalLayout = ({
  children,
  closeModal,
}: {
  children: ReactNode
  closeModal: () => void
}) => {
  return (
    <Overlay onClick={() => closeModal()}>
      <Container
        onClick={e => e.stopPropagation()}
      >
        {children}
      </Container>
    </Overlay>
  )
}

 

 

 


728x90
반응형