[Fend] Modal 컴포넌트 만들기

oaksusu·2024년 5월 27일
0

Fend

목록 보기
5/5
post-thumbnail

0. react에서 Modal창 만들려면?

모달창 밑에 있는 외부 컨텐츠들(예시코드에서 mainContent)은 가리고
(스타일상으로 가리는것뿐만 아니라 접근성 측면에서도 외부 컨텐츠들과 분리하려면)

<div class="mainContent">
	...
</div>
<Modal>
</Modal>

이런식으로 마크업이 되어야할텐데,
메인컨텐츠 내부 컴포넌트 어딘가에서 버튼을 클릭할때마다
Modal창을 제대로 위치시키려면 어떻게 해야할지 고민이였다.

이전에는 Modal의 마크업 위치를 고정시켜두고,
간단한 알림내용만 보여주는 경우에는 몇개의 고정된 데이터(open, title, content, button)들을
redux-toolkit을 사용해서 알림창을 필요로하는 컴포넌트에서 상태변경만 해줬다.

그런데,
모달창안에 title, content, button뿐만 아니라 input이나 다양한 요소들을 넣고 싶을땐
모달이라는 컴포넌트를 만들어두고, children으로 내부를 좀더 자유롭게 쓰면 좋겠는데
그러면 모달창은 마크업상 위치를 최상단에 두려면 어떻게 해야하나 방법을 찾게 되었다.

1. createPortal API

1-1. 작동방식

: 일부 자식을 DOM의 다른 부분으로 렌더링할 수 있게 해줌 (최상단에 엘리먼트를 위치시킬 수 있음)
포털은 DOM노드의 물리적 배치를 변경하는 것임

1-2. 사용법

<div>
  <SomeComponent />
  {createPortal(children, domNode, key?)}
</div>

1-3. 적용

import { createPortal } from "react-dom";

type props = {
  id: string,
  title: string,
  children: React.ReactNode
}

function Modal ({id, title, children}: props) {
  return (
    <div>
      {
        createPortal(
          <div style={{}}>
            <div role="dialog" aria-labelledby={`label_${id}`} aria-modal="true">
              <h2 id={`label_${id}`}>{title}</h2>
              {children}
            </div>
          </div>,
          document.body
        )
      }
    </div>
  )
}
export default Modal

1-4. 막혔던 부분

원인 : children 타입 지정

props중 children에 바로 React.ReactNode 타입 지정해줬을때 자꾸 에러가 났다.

function Modal ({children}: React.ReactNode) { 

그래서 다른 사람들 코드보고
타입 별칭으로 만들어두고 사용하니 에러가 안났다.

type props = {
  children: React.ReactNode
}

function Modal ({children}: props) {

해결 : children은 props에서 구조분해 할당으로 받아온것이 포인트!

그렇기 때문에 props에 대한 타입은 객체로 지정하고,
children은 React.ReactNode로 타입지정을 아래와 같이 해줘야함.

function Modal({ children }: { children: React.ReactNode }) {}

2. Dialog 태그

2-1. 특징

  • 굳이 모달창을 최상단에 위치시키지 않아도 모달외의 컴포넌트와 상호작용할 수 없게 해줌.
  • useRef를 사용해서 모달 요소에 접근해야함.
  • 메서드 : open, showModal, close가 존재함.
  • 딤드처리된 스타일이 기본으로 들어가 있음. (단, open이 아닌 showModal 메서드를 사용해야함) +커스텀가능하다고함!

2-2. 사용법

<button type="button" onClick={() => modal.current.showModal()}>모달열기</button>
<dialog ref={modalRef}>
	...
  <button type="button" onClick={() => modal.current.close()}>모달닫기</button>
</dialog>

2-3. 적용

const modalRef = useRef<HTMLDialogElement>(null)

const handleCloseModal = () => {
  modalRef.current?.close();
}

const handleFindIdClick = () => {
  modalRef.current?.showModal();
};

return (
  <>
    <div>
      <h3>로그인</h3>
      <LoginForm />
      <button type="button" onClick={handleFindIdClick}>
        아이디 찾기
      </button>
    </div>
    <dialog ref={modalRef}>
      <input type="text" /> 
      <div>
        <button type="button">확인</button>
        <button type="button" onClick={handleCloseModal}>닫기</button>
      </div>
    </dialog>
  </>
);

2-4. 막혔던 부분

원인 : 'modalRef.current' is possibly 'null' 에러

해결 : 옵셔널 체이닝

modalRef.current?.close();

참고

wai-modal접근성
React로 모달 구현하기 - CreatePortal과 Dialog 비교
react-ko.dev 비공식 한글 번역 사이트-creatPortal
[TypeScript] 리액트 children 타입 지정해주기 - 타입별 특징
HTML 요소 참고서-dialog
useRef : Object is Possibly null 오류와 관련해서

profile
삐약

0개의 댓글

관련 채용 정보