🦁

[Output]Reactでモーダルを作ってみた。

2024/07/11に公開

Reactの学習備忘録としてモーダルを作成しました。
また記事のベースは自分がメモしたことをChatGPTに投げて文章構成を組んでいるので一部機械っぽいかもしれないです💦

前提

ほしい機能や使用したい技術

  • createPortalを使用する
  • コンテンツの中身にヘッダーやフッターを追加できてカスタマイズみたいな形で汎用性を高める。
  • 高さなどをpropsで制御をする。

使用技術

  • React
  • Typescript
  • styled-components

各コンポーネント

App.tsx

import { useState } from "react";
import styled from "styled-components";
import Modal from "./components/Modal";
import Portal from "./components/Portal";
import ModalContents from "./components/ModalContents";
import ModalHeader from "./components/ModalHeader";
import ModalFooter from "./components/ModalFooter";

const Button_ModalOpenButton = styled.button`
  margin-top: 1rem;
  background-color: #fff;
  border: 2px solid #000;
  border-radius: 8px;
  padding: 8px 15px;
  cursor: pointer;
  transition: all 0.3s ease-in-out;
  &:hover {
    color: #fff;
    background-color: red;
  }
`;

function App() {
  const [editIsOpen, setEditIsOpen] = useState(false);

  function handleClose() {
    setEditIsOpen(false);
  }
  
  return (
    <>
      <Portal>
        <Modal isOpen={editIsOpen} handleClose={handleClose}>
          <ModalHeader>ヘッダー</ModalHeader>
          <ModalContents>コンテンツ</ModalContents>
          <ModalFooter>フッター</ModalFooter>
        </Modal>
      </Portal>
      <Button_ModalOpenButton
        onClick={() => {
          setEditIsOpen(true);
        }}
      >
        モーダルオープン
      </Button_ModalOpenButton>
    </>
  );
}

export default App;

ModalContents.tsx

import React, { ReactNode } from "react";
import styled from "styled-components";

interface ModalContentsInterface {
  height?: number;
  children: ReactNode;
}

const Div_ModalContents = styled.div<{ $height: number }>`
  position: relative;
  overflow-y: scroll;
  box-sizing: border-box;
  max-height: ${(props) => `${props.$height}px`};
  padding-right: 10px;
`;

const Div_ModalContentsInner = styled.div``;

const ModalContents = ({ children, height = 400 }: ModalContentsInterface) => {
  return (
    <Div_ModalContents className="contents" $height={height}>
      <Div_ModalContentsInner className="inner">
        {children}
      </Div_ModalContentsInner>
    </Div_ModalContents>
  );
};

export default ModalContents;

ModalFooter.tsx

import { ReactNode } from "react";
import styled from "styled-components";

interface ModalFooterInterface {
  children: ReactNode;
}

const Div_ModalFooter = styled.div`
  padding: 10px 20px 0;
`;

const ModalFooter = ({ children }: ModalFooterInterface) => {
  return <Div_ModalFooter>{children}</Div_ModalFooter>;
};

export default ModalFooter;

ModalHeader.tsx

import { ReactNode } from "react";
import styled from "styled-components";

interface ModalHeaderInterface {
  children: ReactNode;
}

const Div_ModalHeader = styled.div`
  padding: 10px 20px;
`;

const ModalHeader = ({ children }: ModalHeaderInterface) => {
  return <Div_ModalHeader>{children}</Div_ModalHeader>;
};

export default ModalHeader;

Portal.tsx

import { ReactNode } from "react";
import { createPortal } from "react-dom";

interface HtmlPortalInterface {
  children: ReactNode;
}

const HtmlPortal = ({ children }: HtmlPortalInterface) => {
  return createPortal(children, document.body);
};

export default HtmlPortal;

参考記事

MaterialUIとreact-modalを使ってサクっとモーダル画面をつくる - asoview! Tech Blog
[styled-componentsを使う時のポイント]
https://fe-designer.com/article/styledcomponents/

課題・今後

  • モーダル内に別のコンポーネントをいれて、連動した動きなどを実装してみたい。

まとめ

機能として実装するのには20分もかからなかったですが、参考記事の命名規則に出会うまで命名にすごく悩んで時間がかかっていました。コンポーネント名とCSSの命名が重なってしまう懸念がありましたがタグ名を先頭に記載するというハックを知ってすっきりました。
また機能として実装したいことが実装途中に不安のごとくアイデアとしてでてくるので、ほどほどに進めれればと思っています。

Discussion