In our previous post, we learned about the syntax of callback refs and how to use them to create an input counter component. Today, we'll explore another useful application of callback refs by building a real-life component: a modal.
A modal is a type of dialog box that appears in the foreground of a web page or application. It's perfect for displaying important information, asking for user input, or confirming an action before proceeding. Modals can also be used to showcase media files like images and videos.
There are many common uses for modals, including login and registration forms, displaying terms and conditions, showcasing product details, and confirming actions like deleting data. The great thing about using a Modal instead of a traditional alert box is that it provides more control over the appearance of the popup, allowing developers to customize it to fit their needs.
So are you ready to dive in and learn more about building modals with callback refs? Let's get started!
Organizing modal markup
Let's begin our journey by organizing the markup for a modal. A modal typically includes an overlay with content placed inside it.
<div className="modal__overlay">
<div className="modal__content">
{/* Content of the modal */}
</div>
</div>
The overlay element in a modal is super important. It covers the whole page with a dark background, which makes the modal content stand out and keeps users from getting distracted by the rest of the page. Plus, it stops users from clicking on anything behind the modal, so they can only focus on what's inside. This helps create a feeling of exclusivity and directs attention to the main message or action in the modal.
To make all of this happen, just use this nifty CSS code for the overlay:
.modal__overlay {
background: rgba(30 41 59 / 0.7);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
align-items: center;
display: flex;
justify-content: center;
cursor: pointer;
}
The modal__overlay
CSS class creates the dark background that appears behind the modal content. We use the background
property to set the color and opacity of the overlay. In this case, we're using a semi-transparent black color to achieve the desired effect.
To make sure the overlay covers the whole page, we use position
, top
, left
, height
, and width
properties. By setting position
to fixed
, we ensure the overlay stays in place even when users scroll.
We center the modal content using the align-items
and justify-content
properties. These properties make sure the content is perfectly centered both horizontally and vertically.
Lastly, we set the cursor
property to pointer
to let users know they can click on the overlay to close the modal.
Closing a modal
When it comes to closing a modal, there are three easy ways to do it:
- Click on the dedicated "Close" button
- Press the Escape key on your keyboard
- Click on the overlay element
For the purposes of this post, we'll focus on the third option and show you how to use callback refs to make it happen. All you need to do is add two different refs to your overlay and content elements, and attach callbacks using the ref
attribute:
<div
className="modal__overlay"
ref={(ele) => (this.overlayEle = ele)}
onClick={this.handleClickOverlay}
>
<div
className="modal__content"
ref={(ele) => (this.contentEle = ele)}
>
...
</div>
</div>
In this code snippet, overlayEle
and contentEle
are variables that reference the overlay and content elements, respectively. When the user clicks on the overlay element, the handleClickOverlay()
function is triggered.
It's important to note that clicking on the overlay will close the element, but if the clicked target is within the content element, nothing will happen. To check if the clicked target is within the content element, we can use the contentEle.contains(clickedTarget)
function, where clickedTarget
is the clicked element retrieved from the target
property of the event object.
Here's an example of what the handleClickOverlay()
function could look like. Don't worry about the close()
function for now, we'll talk about it in the next paragraph.
handleClickOverlay(e) {
if (this.contentEle && !this.contentEle.contains(e.target)) {
// Close the modal
this.close();
}
}
It's easy to keep track of whether the modal is open or closed. We simply use an internal boolean state, like isOpened
. When the page first loads, the modal is closed, so the state is initially set to false
.
constructor(props) {
super(props);
this.state = {
isOpened: false,
};
}
We'll need two functions to handle opening and closing the modal. These functions will set the value for the isOpened
state.
close() {
this.setState({ isOpened: false });
}
open() {
this.setState({ isOpened: true });
}
To close the modal, click on the overlay area in the demo below. Please note that this is just for demonstration purposes and there is no way to reopen it.
Opening a modal
Modals are typically not displayed by default. Instead, we can use a button to trigger the modal. Imagine you have a button and a modal with specific content. When you click the button, the modal will open up.
const handleClickOpenModal = () => {
// Open the modal
};
return (
<button onClick={handleClickOpenModal}>
Open the modal
</button>
<Modal>
...
</Modal>
);
Let's talk about opening a modal in the handleClickOpenModal()
function.
If you have a basic understanding of object-oriented programming (OOP), you know that we can call public methods of a class once we create an instance of it. Here's an example: we can create an instance of a square and calculate its area by calling the getArea()
function.
class Square {
constructor(side) {
this.side = side;
}
getArea() {
return Math.pow(this.side, 2);
}
}
const square = new Square(5);
square.getArea(); // 25
If we take a step back, it's clear that our Modal is a class component. That means we can call its methods, like open()
or close()
, to open or close the modal, as long as we can retrieve its instance.
Thankfully, callback refs aren't just useful when we need to get a reference to a DOM element. They also work to get the instance of a class component. The syntax is the same: set a callback to the ref
attribute.
To get started, let's declare the modalInstance
variable and use a callback ref to set it to the modal it's attached to.
let modalInstance;
return (
<Modal ref={(modal) => modalInstance = modal}>
...
</Modal>
);
In this example, the modalInstance
variable is a reference to the Modal component. To open the modal, all you need to do is call the open()
function. It's as simple as that!
const handleClickOpenModal = () => {
modalInstance.open();
};
Give the demo below a try. To get started, click the button to open the modal. When you're done, simply click the dark overlay behind it to close the modal. Feel free to repeat the process as many times as you'd like.
The limitations of accessing methods in class components
While the approach of using callback refs may work in our example, there are several disadvantages to be aware of.
Firstly, this approach doesn't work with functional components. Since they don't have instances, we cannot access their methods in the same way as we do with class components. This means that using callback refs to get the instance of a functional component won't work.
Secondly, functional programming (FP) is generally better than object-oriented programming (OOP) because there are no side effects. Developing components with FP provides better composition capacity than OOP. Additionally, in JavaScript, there are no real concepts of private and public methods. As you get the instance of the Modal, you can see all of its methods. In general, this is not considered a good practice.
Lastly, this approach is not scalable. Since you have to create a separate variable to reference a modal, you might have to create a lot of variables if the page has many modals.
No need to worry, though. You can achieve the same result in functional components through other means. One way is to use React hooks, specifically the useImperativeHandle
hook. This hook lets you expose certain functions from a functional component to its parent component. We'll explore these solutions in this series as well.
It's highly recommended that you visit the original post to play with the interactive demos.
If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!
If you want more helpful content like this, feel free to follow me:
Top comments (0)