LoginSignup
0
0

More than 1 year has passed since last update.

ReactのPortalをある指定したコンポーネントの上に表示させる

Last updated at Posted at 2022-09-11

はじめに

いつかやろうと思っていたReactに最近向き合って苦戦しております。

今回、Portalを用いて、あるコンポーネントを特定のコンポーネントの上にのみ描画させることができたので、記事にします。

Reactのバージョンは18です。

やりたいこと

まず以下の画面のように、Header(light pink), SideBar(light green), Main(light blue), Footer(light gray)の4つのコンポーネントを持つ画面を作ります。
image.png

そこで、Mainにあるボタンをクリックすると、Mainコンポーネントの上にのみPortalのコンポーネントPortal(white)を表示させてやりたいです。
image.png

実装

まず、各コンポーネントを作っていきます。
Header,Footerの縦は10vh, SideBar, Mainの縦は80vhで、SideBarの横は10vw, Mainを90vwとします。
SideBarとMainはFlexで設置します。

大元となるApp.tsxです。SideBarコンポーネント内にMainコンポーネントを設置します。

App.tsx
import { Footer } from './Footer';
import { Header } from './Header';
import { SideBar } from './SideBar';

function App() {
  return (
    <>
      <Header />
      <SideBar />
      <Footer />
    </>
  );
};
export default App;

次にHeader, Footerを作ります。

Header.tsx
export const Header = () => {
    return (
        <div style={{height: "10vh", backgroundColor: "lightpink"}}>
            Header
        </div>
        
    );
};
Footer.tsx
export const Footer = () => {
    return(
        <div style={{height: "10vh", backgroundColor: "lightgray"}}>
            Footer
        </div>
    )
}

SideBarはFlexレイアウトとします。

SideBar.tsx
import { Main } from "./Main"

export const SideBar = () => {
    return (
        <div style={{display: "flex", height: "80vh"}}>
            <div style={{width: "10vw", backgroundColor: "lightgreen"}}>
            Sidebar
            </div>
            <Main />
        </div>
    )
}

Mainコンポーネントです。ここのポイントはid属性にmainを加えてあげることです。
ボタンと、そのhandlerを作ります。

Main.tsx
import { FC, useState } from "react";
import { Portal } from "./Portal";


export const Main: FC = () => {
    const [showPortal, setShowPortal] = useState(false);
    const handleClick = () => {
        showPortal ? setShowPortal(false) : setShowPortal(true);
    };
    return (
        <div id="main" style={{backgroundColor: "lightblue", width: "90vw"}}>
                { showPortal && <Portal />}
            <div style={{position: "absolute"}}>
                Main<br />
                <button onClick={handleClick}>Show/Hide portal</button>
            </div>            
        </div>
        
    );
};

そして、最後に肝となるPortal部分です。

Portal.tsx
import { FC, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';

const createElement = (): HTMLElement => {
    const el = document.createElement("div");
    el.setAttribute("id", "portal");
    el.setAttribute("style","height: 100%");
    return el;
};

export const Portal: FC = () => {
    const [showPortal, setShowPortal] = useState(false);
    useEffect(() => {
        const el = document.querySelector<HTMLElement>("#portal") ?? createElement();
        const el_main = document.querySelector<HTMLElement>("#main")!;
        el_main.appendChild(el);    
    },[]);    
    useEffect(() => {
        setShowPortal(true);
    },[]);

    if (!showPortal) {
        return null;
    }
    return createPortal(
        <div style={{backgroundColor: "white", height: "100%", textAlign: "right"}}>
            Portal
            </div>
        ,document.getElementById("portal")!);
};

よくあるPortalの例では、Modalを全画面に出力させるというもので、大体はrootの上に描画させるというものでしたが、ここではMainコンポーネントの上にのみ出力したいので、Mainの出力にid: mainを割り振って、それに対してportalをappendChildをしてやることを思いつきました。

また、Portalのサイズについてですが、これを見てわかるように親はMainコンポーネントであるので、Mainに対して高さを100%とすれば良いです。これを実現するにはcreateElementをするときにstyle属性も与えてしまうことで解決できました。

最後に

Reactガチ初心者ですが、なんとかこのようにして狙った挙動をつくることができました。
間違えや助言等ありましたらコメント頂ければ幸いです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0