← Back toMaster of React ref

Access the methods of class components

Written byPhuoc Nguyen
Created
10 Oct, 2023
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.
tsx
<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:
css
.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:
tsx
<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.
tsx
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`.
tsx
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.
tsx
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.
tsx
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.
js
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.
tsx
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!
tsx
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.
If you found this post 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!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn