React Modal Component Implementation

Photo by Lautaro Andreani on Unsplash

Creating React Portal in CRA

Edit public/index.html and add:

   <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<--- Add this --->
<div id="modal-root"></div>
</body>

Creating React Portal in Next.js

Create _document.jsx or _document.tsx if you are using TypeScript.

import Document, { Html, Head, Main, NextScript } from 'next/document'class MyDocument extends Document {
static async getInitialProps(context) {
const initialProps = await Document.getInitialProps(context)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

Creating useOnClickOutside hook

We will create useOnClickOutsidehook which accepts React.SetStateActionas a parameter, we will pass the setShowModal state action. This hook will help us to manage outside clicks, for the inside close click, there will be another handle method.

JS:

Completed hooks/useOnClickOutside.jsfile:

import React from "react";const useOnClickOutside = (handle) => {
const ref = React.useRef(null);
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
handle(e);
}
};
React.useEffect(() => {
window.addEventListener("click", handleClickOutside, true);
return () => {
window.removeEventListener("click", handleClickOutside, true);
};
});
return ref;
};
export default useOnClickOutside;

TypeScript:

Completed hooks/useOnClickOutside.tsfile:

import React from "react";const useOnClickOutside = (
handle: (
e:
| MouseEvent
| React.MouseEvent<HTMLElement>
) => void) => {

const ref = React.useRef<HTMLInputElement>(null);
const handleClickOutside = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
handle(e);
}
};
React.useEffect(() => {
window.addEventListener("click", handleClickOutside, true);
return () => {
window.removeEventListener("click", handleClickOutside, true);
};
});
return ref;
};
export default useOnClickOutside;

Modal Component

Create components/modal.js or tsx. Create modal.css.

import React from "react";
import ReactDOM from "react-dom";
import useOnClickOutside from "../hooks/useOnClickOutside";
import "../modal.css";
const Modal = ({
showModal,
setShowModal,
children,
title = "",
reRenderOnCloseOpen = true
}) => {};
const handleCloseClick = (e) => {
e.preventDefault();
setShowModal(false);
};
const ref = useOnClickOutside(handleCloseClick);
const modalContent = (showModal || !reRenderOnCloseOpen) && (
<div
className={`modal-container ${
!showModal && !reRenderOnCloseOpen ? "hidden" : ""
}
`}
>
<div ref={ref} className="modal-main">
<div className="relative">
<div className="absolute right-0">
<button onClick={handleCloseClick}>x</button>
</div>
</div>
<div>
<div>{title}</div>
<div>{children}</div>
</div>
</div>
</div>
);
return ReactDOM.createPortal(
modalContent,
document.getElementById("modal-root")
);
if (typeof window !== "undefined") {
return ReactDOM.createPortal(
modalContent,
document.getElementById("modal-root")
);
} else {
return null;
}

Completed Modal Component

import React from "react";
import ReactDOM from "react-dom";
import useOnClickOutside from "../hooks/useOnClickOutside";
import "../modal.css";
const Modal = ({
showModal,
setShowModal,
children,
title = "",
reRenderOnCloseOpen = true
}) => {
const handleCloseClick = (e) => {
e.preventDefault();
setShowModal(false);
};
const ref = useOnClickOutside(handleCloseClick);
const modalContent = (showModal || !reRenderOnCloseOpen) && (
<div
className={`modal-container ${
!showModal && !reRenderOnCloseOpen ? "hidden" : ""
}
`}
>
<div ref={ref} className="modal-main">
<div className="relative">
<div className="absolute right-0">
<button onClick={handleCloseClick}>x</button>
</div>
</div>
<div>
<div>{title}</div>
<div>{children}</div>
</div>
</div>
</div>
);
return ReactDOM.createPortal(
modalContent,
document.getElementById("modal-root")
);
};
export default Modal;
interface Props {
showModal: boolean;
setShowModal: React.Dispatch<React.SetStateAction<boolean>>;
title?: string;
reRenderOnCloseOpen?: boolean;
}

Styling

Edit the modal.css and add these:

.modal-container {
position: fixed;
top: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.modal-main {
width: 18rem;
height: 20rem;
border-radius: 0.5rem;
padding: 1rem;
background-color: #464646;
color: white;
}
.flex {
display: flex;
}
.hidden {
display: none;
}
.right-0 {
right: 0;
}
.relative {
position: relative;
}
.absolute {
position: absolute;
}
import Modal from "./components/Modal";
import React from "react";
export default function App() {
const [showModal, setShowModal] = React.useState(false);
return (
<div className="App">
<button
onClick={() => {
setShowModal(true);
}}
>
Open Modal
</button>
<Modal
title="Example Modal"
showModal={showModal}
setShowModal={setShowModal}
>
<div>ModalContent</div>
</Modal>
</div>
);
}

Final Words

In this tutorial, we implemented a simple yet powerful Modal. You can improve it as you want if you get the main idea.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store