[React] React Portal

๐Ÿ—’๏ธ React Potal ์ด๋ž€?

  • ๋‹ค๋ฅธ ๊ณณ์— ์žˆ๋Š” element๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ข…์†๋˜์–ด ์žˆ๋Š” ๋” ํŠธ๋ฆฌ๋ฅผ ๋ฒ—์–ด๋‚˜ ์™ธ๋ถ€์˜ ๋‹ค๋ฅธ ๋”์œผ๋กœ ๋ Œ๋”ํ•œ๋‹ค. (์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํ•˜๋‚˜์ง€๋งŒ ๋ถ€๋ถ„์ ์œผ๋กœ ๋‹ค๋ฅธ ๋”์— ๋งˆ์šดํŠธ)

๐Ÿ—’๏ธ useModal hook

import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { styled } from "styled-components";

const useModal = () => {
  const [modalOpened, setModalOpened] = useState(false);

  const openModal = () => {
    setModalOpened(true);
  };

  const closeModal = () => {
    setModalOpened(false);
  };

  interface IProps {
    children: React.ReactNode;
  }

  const ModalPortal: React.FC<IProps> = ({ children }) => {
    const ref = useRef<Element | null>();
    const [mounted, setMounted] = useState(false);

    useEffect(() => {
      setMounted(true);
      if (document) {
        // dom์— root-modal์ด ์žˆ์œผ๋ฉด ๊ฐ€์ ธ์˜ค๊ธฐ
        const dom = document.querySelector("#root-modal");
        ref.current = dom;
      }
    }, []); // mount

    if (ref.current && mounted && modalOpened) {
      return createPortal(
        <Container>
          <div
            className="modal-background"
            role="presentation"
            onClick={closeModal}
          />
          {children}
        </Container>,
        ref.current
      );
    }
    return null;
  };

  return {
    openModal,
    closeModal,
    ModalPortal,
  };
};

export default useModal;

const Container = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 11;
  .modal-background {
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.75);
  }
`;

๐Ÿ—’๏ธ useModal hook ์‚ฌ์šฉํ•˜๊ธฐ

import { styled } from "styled-components";
import AirbnbLogoIcon from "../public/static/svg/logo/logo.svg";
import AirbnbLogoTextIcon from "../public/static/svg/logo/logo_text.svg";
import Link from "next/link";
import palette from "../styles/palette";
import { useState } from "react";
import useModal from "../hooks/useModal";
import SignUpModal from "./auth/SignUpModal";

const Header: React.FC = () => {
  const { openModal, ModalPortal } = useModal();
  return (
    <Container>
      <Link href={"/"}>
        <div className="header-logo-wrapper">
          <AirbnbLogoIcon className="header-logo" />
          <AirbnbLogoTextIcon />
        </div>
      </Link>
      <div className="header-auth-buttons">
        <button
          type="button"
          className="header-sign-up-button"
          onClick={openModal}
        >
          ํšŒ์›๊ฐ€์ž…
        </button>
        <button type="button" className="header-login-button">
          ๋กœ๊ทธ์ธ
        </button>
      </div>
      <ModalPortal>
        <SignUpModal />
      </ModalPortal>
    </Container>
  );
};

export default Header;

const Container = styled.div`
  position: sticky;
  top: 0;
  width: 100%;
  height: 80px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 80px;
  background-color: white;
  box-shadow: rgba(0, 0, 0, 0.08) 0px 1px 12px;
  z-index: 10;
  .header-logo-wrapper {
    display: flex;
    align-items: center;
    .header-logo {
      margin-right: 6px;
    }
  }

  .header-auth-buttons {
    .header-sign-up-button {
      height: 42px;
      margin-right: 8px;
      padding: 0 16px;
      border: 0;
      border-radius: 21px;
      background-color: white;
      cursor: pointer;
      outline: none;
      &:hover {
        background-color: ${palette.gray_f7};
      }
    }
    .header-login-button {
      height: 42px;
      padding: 0 16px;
      border: 0;
      box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.18);
      border-radius: 21px;
      background-color: white;
      cursor: pointer;
      outline: none;
      :hover {
        box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.12);
      }
    }
  }
`;

์ฐธ๊ณ 

  • ํด๋ก  ์ฝ”๋”ฉ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” Next.js

Leave a comment