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" />
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" />
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;
}
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} />
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>
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>
To make the uploader completely invisible, we can add a CSS style to the class:
.container__uploader {
display: none;
}
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} />
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();
}
};
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) => {
...
});
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>
});
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:
Top comments (0)