This tutorial is a continuation of the previous tutorial, where I created a Modal component in a Next.js 13 application, using the pages
router. In the current tutorial, I will enhance it by allowing it to close when the user clicks outside of it. I will refer to this functionality as “close on backdrop click”.
Enhance the Modal
Here is the updated components/Modal.js
. I will add explanations below the code:
import React, {useCallback, useEffect} from "react";
import ReactDOM from "react-dom";
const Modal = ({ onClose, children, title }) => {
// create ref for the StyledModalWrapper component
const modalWrapperRef = React.useRef();
// check if the user has clicked inside or outside the modal
// useCallback is used to store the function reference, so that on modal closure, the correct callback can be cleaned in window.removeEventListener
const backDropHandler = useCallback(e => {
if (!modalWrapperRef?.current?.contains(e.target)) {
onClose();
}
}, []);
useEffect(() => {
// We wrap it inside setTimeout in order to prevent the eventListener to be attached before the modal is open.
setTimeout(() => {
window.addEventListener('click', backDropHandler);
})
}, [])
useEffect(() => {
// remove the event listener when the modal is closed
return () => window.removeEventListener('click', backDropHandler);
}, []);
const handleCloseClick = (e) => {
e.preventDefault();
onClose();
};
const modalContent = (
<div className="modal-overlay">
{/* Wrap the whole Modal inside the newly created StyledModalWrapper
and use the ref */}
<div ref={modalWrapperRef} className="modal-wrapper">
<div className="modal">
<div className="modal-header">
<a href="#" onClick={handleCloseClick}>
x
</a>
</div>
{title && <h1>{title}</h1>}
<div className="modal-body">{children}</div>
</div>
</div>
</div>
);
return ReactDOM.createPortal(
modalContent,
document.getElementById("modal-root")
);
};
export default Modal
First I’ve attached a ref to the .modal-wrapper
element in components/Modal.js
. We need this ref
in order to be able to check whether the mouse click was inside or outside the modal.
The second step is to create the backdropHandler
function, which will perform the check whether the click is in or out of the modal window:
const backDropHandler = useCallback(e => {
if (!modalWrapperRef?.current?.contains(e.target)) {
onClose();
}
}, []);
How does this check work? e.target is the element that the user has clicked on. If this element is not within the children of the .modal-wrapper
element, this means that the user has clicked outside the modal. So we can close it by invoking the onClose() function.
The last part is to attach an eventListener to the window object on the initial render and then remove this listener when the Modal is closed. Essentially, every click in our app invokes backDropHandler()
, which detects whether the click was inside the modal window or not. Make sure to clean up eventListeners
in order to avoid memory leaks in our app :
useEffect(() => {
// We wrap it inside setTimeout in order to prevent the eventListener to be attached before the modal is open.
setTimeout(() => {
window.addEventListener('click', backDropHandler);
})
}, [])
useEffect(() => {
// remove the event listener when the modal is closed
return () => window.removeEventListener('click', backDropHandler);
}, []);
And here is a short demo:
Repository
As the tutorial is split into two, you might find it easier to check out the demo in the GitHub repo that I’ve created.
If you are using older version of Next.js, you can use the repository from the previous version of this tutorial. Make sure to run the code from the old repo only with Node v14.
Final words
This is not the only way to implement the close on backdrop click functionality but this is the simplest way to achieve it in my opinion. Some developers might suggest using the <Dialog> HTML tag which has a really nice API. Unfortunately, it’s still not well supported and requires a pollyfill and I prefer to create my own implementation instead of using third party code. I hope, the support will improve in the comming years.
I hope you enjoyed this tutorial. Don’t forget to check my blog for more dev recipes.
Part 1 “Modal component with Next.js”: