Pass a ref to a child component using forwardRef()

phuocng

Phuoc Nguyen

Posted on December 12, 2023

Pass a ref to a child component using forwardRef()

When working with React, there are instances where you'll need to pass a reference to a child component. This is helpful when you want the child component to access properties or methods of the parent component. One way to accomplish this is by using the forwardRef() method.

In this post, we'll explore this pattern by building an Uploader component that enables users to select a file from their computer. Let's get started!

Creating a simple file uploader component

File upload is a popular feature in web development that allows users to easily transfer files from their computer to a website or application. It's used across various industries, from e-commerce to healthcare to education, for uploading documents, images, videos, and more. In this post, we'll focus on building an Uploader component that simplifies the process of choosing a file from your computer. We'll keep it simple and won't dive into any complex file-related operations.

To upload one or more files, we simply need to use an input element with the type attribute set to file.

<input type="file" />
Enter fullscreen mode Exit fullscreen mode

Although the file input is functional, it lacks style and customization options, making it difficult to integrate with our application's design language.

To solve this problem, we'll create an Uploader component that replaces the file input with a sleek and polished button. By using a button, we can take advantage of CSS to create a visually appealing and user-friendly interface. Buttons also allow us to add icons or text that provide additional context or instructions to the user.

In addition to the file input, the Uploader component renders a button with a more attractive appearance, like this:

<button className="uploader__button">Choose a file</button>
<input className="uploader__input" type="file" />
Enter fullscreen mode Exit fullscreen mode

The uploader__button and uploader__input CSS classes are associated with the button and file input, respectively. We can easily customize their appearance by using these classes.

To hide the input, simply set its display property to none.

.uploader__input {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

You now have complete control over the appearance of the entire component. Simply modify the corresponding CSS class (uploader__button) to match your preferred style.

But wait, how can we trigger the file dialog when the file input is invisible? Easy - we handle the click event of the button instead. Within the handler, we'll trigger the click event of the input.

To make this happen, we first create a reference to the file input element using the useRef() hook. We then attach this reference to the file input using the ref attribute.

const inputRef = React.useRef();

// Render
 <input className="uploader__input" ref={inputRef} />
Enter fullscreen mode Exit fullscreen mode

To make our custom button open up the file dialog box when clicked, we need to define a function called handleClick. This function first gets a reference to the file input element we created earlier. If the reference exists, we call its click() method, which opens the file dialog box.

To make sure our custom button triggers the handleClick function, we add an event listener to it that listens for clicks. When the button is clicked, it calls the handleClick function, which in turn opens up the file dialog box for users to choose a file from their computer.

Here's the sample code to help you understand better:

const handleClick = () => {
    const inputEle = inputRef.current;
    if (inputEle) {
        inputEle.click();
    }
};

// Render
<button onClick={handleClick}>Choose a file</button>
Enter fullscreen mode Exit fullscreen mode

Give this button a click and watch what happens. It'll open up a file dialog box. Don't worry, we're not going to do anything with the files you select. This is just a demonstration, after all.

Forwarding a reference

Let's say we want to replace the button in a certain situation. In addition to the usual way of adding a new prop to the Uploader for button customization, we can use the forwardedRef() method. In this section, we'll explore how to do that.

But first, let's assume that the Uploader component is placed within a container that also includes an SVG icon. No need to stress over the handleClickContainer() function, we'll go over it shortly.

<div className="container" onClick={handleClickContainer}>
    <svg className="container__icon">
        ...
    </svg>
    <div className="container__uploader">
        <Uploader />
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

To make the uploader completely invisible, we can add a CSS style to the class:

.container__uploader {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

We want users to be able to open the file dialog by clicking the container element, just like they can by clicking the button inside the uploader. To make this happen, we can create a reference to the Uploader component using the useRef() hook, and attach it to the Uploader component via the ref attribute.

const uploaderRef = React.useRef();

// Render
<Uploader ref={uploaderRef} />
Enter fullscreen mode Exit fullscreen mode

When a user clicks on the container, the handleClickContainer function is triggered. This, in turn, invokes the click function of the main button within the Uploader.

const handleClickContainer = () => {
    const uploadBtn = uploaderRef.current;
    if (uploadBtn) {
        uploadBtn.click();
    }
};
Enter fullscreen mode Exit fullscreen mode

This message will appear in the browser Console until we can achieve the desired functionality through our imagination in React:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()

In this case, React not only throws an error but also provides a helpful solution. It suggests using forwardRef(), which is designed for this use case. To implement this, you'll need to modify the Uploader component to support forwarding the ref.

const Uploader = React.forwardRef((props, ref) => {
    ...
});
Enter fullscreen mode Exit fullscreen mode

The forwardRef() method is a function that takes two parameters. The first parameter is props, which is the same as what you usually pass to the component. The second parameter is ref, which is the reference you want to expose so that other components can access the underlying node from outside.

In order to trigger the click function of the main button, we'll need to pass the ref down to it using the ref attribute.

const Uploader = React.forwardRef((props, ref) => {

    // Render
    <button ref={ref}>...</button>
});
Enter fullscreen mode Exit fullscreen mode

Check out the demo below. To see the file dialog box, simply click on the entire container.

Conclusion

Using forwardRef() gives us more control over a child component's behavior and appearance by allowing us to pass a reference to it. It's also handy for integrating third-party libraries into our application when we don't have control over their implementation. This way, we can access the underlying node or component and perform the tasks we want.

This pattern is particularly useful when building reusable components that other developers can customize. With forwardRef(), we can expose specific parts of a component without revealing the nitty-gritty implementation details. This makes it easier for other developers to use our components in their projects.


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:

πŸ’– πŸ’ͺ πŸ™… 🚩
phuocng
Phuoc Nguyen

Posted on December 12, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related