[TS] React Portals 이용하여 modal 구현해보기 👷‍♀️

Beanxx·2022년 11월 26일
0

React

목록 보기
2/6
post-thumbnail

한달 전쯤에 Udemy React 강의를 통해 portal로 modal를 구현하는 새로운 방법에 대해 알게 되었었다.
이 후에, 타스 플젝에 모달을 적용해야 해서 이왕 구현하는거 portal를 이용해서 모달을 구현해보기로 했다.

🔗 React Portals 공식문서

💡 Portal이란?

  • 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 방법
  • createPortal을 사용하여 해당 컴포넌트의 HTML 내용을 다른 곳으로 포털, 즉 이동시킬 수 있다!

Portal을 이용하면 DOM 트리 상에서 부모 컴포넌트의 영향을 받지 않도록 최상위 계층으로 컴포넌트를 옮김으로써 원치 않는 CSS 등의 방해를 받지 않을 수 있다는 것이 장점!! 👍

ReactDOM.createPortal(child, container)
// 첫 번째 인자(child): 렌더링할 수 있는 React 자식
// 두 번째 인자(container): DOM 엘리먼트

먼저, Portal를 사용하려면 public > index.html 파일 내 body 태그 내에 modal에 해당하는 rootmodal 뒷 배경에 해당하는 root를 설정해준다.

<!-- index.html -->
<body>
  <div id="backdrop-root"></div>
  <div id="overlay-root"></div>
  <div id="root"></div>
</body>

이후에, 모달 뒷배경에 해당하는 컴포넌트를 설정해준다. 타스로 구현하는 것이므로 type도 설정해주기!

// 모달 뒷배경
interface Prop {
  onClose?: () => void
}

const Backdrop: React.FC<Prop> = ({ onClose }) => {
  return <S.Backdrop onClick={onClose} />
}

🚨 [Error handling] Visible, non-interactive elements with click handlers must have at least one keyboard listener.

윗 부분 구현하던 도중에 아래와 같은 에러가 났었는데 이 에러는 div 태그에 onClick을 사용해서 나는 에러였다.
👀 해결 방법: div 태그를 button 태그로 바꿔주니까 에러 해결~!

Visible, non-interactive

다음으로, 모달 부분에 해당하는 컴포넌트를 구현해준다. + type까지 지정해주기!

// 모달 부분
interface Props {
  title?: string
  value?: string
  children?: React.ReactNode
  onConfirm?: () => void
  onClose?: () => void
  onChange?: (e: React.FormEvent<HTMLInputElement>) => void
}


// 여기서 props로 넘겨주고, 타입 설정하면 에러나서 하나하나씩 변수를 통해 props로 넘겨줘야 했다.
const ModalOverlay: React.FC<Props> = ({ title, children, onConfirm, value, onChange, onClose }) => {
  return (
    <S.Container>
      <header>
        <h2>{title}</h2>
      </header>
      <main>
        <p>{children}</p>
        <LoginInput value={value} onChange={onChange} />
      </main>
      <footer>
        <Button btnType="highlighted" onClick={onClose}>
          취소
        </Button>
        <Button btnType="highlighted" onClick={onConfirm}>
          확인
        </Button>
      </footer>
    </S.Container>
  )
}

마지막으로, portal로 modal 구현하는 과정에서 젤 중요한 부분!
위에서 구현했던 두 컴포넌트에 각각 createPortal()을 적용해준다.

// portal을 이용한 모달 기능 구현
import React from "react"
import ReactDOM from "react-dom"

// props type은 ModalOverlay 컴포넌트에서 사용하는 props 종류와 같으므로 전에 선언해주었던 Props 타입을 재사용하면 된다!
const Modal: React.FC<Props> = ({ title, children, onConfirm, value, onChange, onClose }) => {
  return (
    <div>
      {ReactDOM.createPortal(
        <Backdrop onClose={onClose} />,
        document.getElementById("backdrop-root") as HTMLElement
      )}
      {ReactDOM.createPortal(
        <ModalOverlay
          title={title}
          onConfirm={onConfirm}
          value={value}
          onChange={onChange}
          onClose={onClose}
        >
          {children}
        </ModalOverlay>,
        document.getElementById("overlay-root") as HTMLElement
      )}
    </div>
  )
}

export default Modal;

🚨 [Error handling] Argument of type ‘HTMLElement | null’ is not assignable to parameter of type ‘Element | DocumentFragment’.

윗 부분 구현하던 도중에 document.getElementById() 부분에서 에러 발생 😵‍💫
👀 해결 방법: document.getElementById("backdrop-root") as HTMLElement 요렇게 뒤에 as를 통해 타입을 지정해주니까 에러 해결~!

Argument of type

요렇게 구현하면 모달 구현 끄읏!

  • 아래 화면과 같이 모달이 닫혔을 땐 backdrop-root & overlay-root에 요소가 없다가 모달이 열리면 요소가 추가되는 것을 확인할 수 있다.
  • 다른 컴포넌트들이 렌더링되는 일반 root에 생기는 것이 아니라 따로 설정해둔 backdrop-root & overlay-root에 요소가 추가되는 것이 중요!!
스크린샷 2022-11-27 오후 8 17 04 스크린샷 2022-11-27 오후 8 16 23

생각보다 타스로 변경하면서 에러도 많이 나고, 모달 내에 input도 넣어야 해서 수월하진 않았지만 직접 구현해보면서 portal 적용법을 알 수 있었다. 🙌

profile
FE developer

0개의 댓글