Uploading files with React Hook Form

Have you ever implemented a form validation in React? Then you’ve probably heard of React Hook Form.  Most software products nowadays contain forms for submitting user data. There are different libraries for React that help us improve the user experience: Formik, React hook form, Final Form etc. They make validation easier, also improving UX in […]

by Martin Milchov

June 13, 2023

5 min read

React Hook Form - Uploading files with React Hook Form

Have you ever implemented a form validation in React? Then you’ve probably heard of React Hook Form. 

Most software products nowadays contain forms for submitting user data. There are different libraries for React that help us improve the user experience: Formik, React hook form, Final Form etc. They make validation easier, also improving UX in the process.

Today, we’ll take a look at the React Hook Form (react-hook-form). The library has a lot of advantages: it helps us create good-looking and fast forms, reduces rendering, improves UX, and much more.

Let’s start with a brief technical introduction. 

One of the key React Hook Form concepts is the register – you should register all your components, making their value available for both the form and the submission. 

Another important thing is that the library uses references instead of props to share these values with the components, which reduces rendering.

In addition, there is very good documentation available, including many different examples. One of them is explained in this video on how to handle uploaded files with React hook form.

In this article, we’re going to focus on creating a form for uploading files – but with a friendlier UX than in the example above. We’ll use material UI components, but you can use whatever components/ libraries you want.

The form we will create is a simple one with a few fields: name, age, email, city, profile picture and documents.

ZncW4KQQ gN6ZBUp6HrL3ZJrWKrEl5V YccjRG WXRnU00SRiNzN4i2tg Z5HIHtz7g5M0Q3giayY9psO6gBhqgCrbNsTYsutjBduE xJoWfE4 qhqOOg4bmqJABgTCwXrto2L - Uploading files with React Hook Form

Adding validation to the fields is an easy task. To do this, we need to specify the validation we want in the register of the form. For more details about the validation, you can take a look at the documentation https://react-hook-form.com/.

For our example, let’s add the following validations:

  • name – be required
  • age – be required and be an integer between 18 and 65
  • email – be required and add default email validation “text@domain”
  • city – be required

All these validations are passed as properties in the register of the form. In addition to each field, we can specify error messages to improve the UX.

<TextField
       {...register("age", { required: true, min: 18, max: 65 })}
       required
       label="Age"
       type="number"
       variant="outlined"
       error={!!errors.age}
     />
     {errors.age && errors.age.type === "required" && (
       <Alert severity="error">This field is required.</Alert>
     )}
     {errors.age && ["min", "max"].includes(errors.age.type) && (
       <Alert severity="error">
         You must be older than 18 and younger than 65 years old.
       </Alert>
     )}


Now we come to the interesting part – handling the uploaded files. First, let’s create a functionality for uploading a profile picture. The data of the uploaded file will be stored in <input type=”file”> element, but as we know, the default view of this element looks very poor and unfriendly.

R0NFkQDXJV223fdi6D5Xrlro1wDtfPZ Fwqu8 JXgb qjA8tJWnyGgqJac9gZEGQYouPEvh3Qq7jIkB5EMN5b2SjRKXyhWYG4QTNC28E1p Zia6qB64MP9mHdkpNuIpqObjsKvpeMulEhlVqzFYsH8U - Uploading files with React Hook Form

Here is our plan to achieve better UX:

  1. Use our own component instead of the default <input>.
  2. Connect the component with the input.
  3. Add the uploaded file in the form state (or connect the input with the form “register”).
  4. Add а preview of the uploaded file.

Following our plan, let’s hide the default input with CSS. Our HiddenInput component has applied “display: none”. Then we add a Button component from the material UI and connect both through the reference hiddenInputReference. To keep the uploaded file in the form state, we also add the HiddenInput field into the form through the register

Here’s the interesting part – the React Hook Form library manages the state of the form using references. Usually, this happens automatically when you set the register property to the field (like we did for name, age etc.). To keep the connection between the Button and the HiddenInput, while keeping the HiddenInput registered in the form state at the same time, we need to pass both references – the hiddenInputRef and the registerRef. This way, when you click on the Button, it’ll trigger the HiddenInput and the user can upload the file. Once the file is uploaded, it’s added to the form state.

To complete the profile picture functionality, we add a preview and Avatar component to show it.

const ProfilePicture = ({ register }) => {
 const hiddenInputRef = useRef();


 const [preview, setPreview] = useState();


 const { ref: registerRef, ...rest } = register("profilePicture");


 const handleUploadedFile = (event) => {
   const file = event.target.files[0];


   const urlImage = URL.createObjectURL(file);


   setPreview(urlImage);
 };


 const onUpload = () => {
   hiddenInputRef.current.click();
 };


 const uploadButtonLabel = 
  preview ? "Change image" : "Upload image";


 return (
   <ProfilePictureContainer>
     <Label>Profile picture</Label>


     <HiddenInput
       type="file"
       name="profilePicture"
       {...rest}
       onChange={handleUploadedFile}
       ref={(e) => {
         registerRef(e);
         hiddenInputRef.current = e;
       }}
     />


     <Avatar src={preview} sx={{ width: 80, height: 80 }} />


     <Button variant="text" onClick={onUpload}>
       {uploadButtonLabel}
     </Button>

 </ProfilePictureContainer>
 );
};

Now we know how to handle a single file. But what if we want to upload more than one? The React Hook Form offers a simple solution through the useFieldArray hook. The hook is used when you want to handle arrays of data. The flow is pretty similar to the above – we use a HiddenInput allowing multiple files, a Button component for better UX, hiddenFileInput reference connecting both. 

To make things more interesting, let’s add a functionality letting us drop some files in case we upload the wrong ones.

First, let’s display the file names using the Grid component and add the DeleteForeverIcon button for dropping an uploaded file. If the component we’re using (Grid) doesn’t expose the input’s ref, then we should use the Controller component from React hook form, which will take care of the registration process.

const Documents = ({ control }) => {
 const { fields, append, remove } = useFieldArray({
   control,
   name: "documents",
   keyName: "documentId"
 });


 const hiddenFileInput = useRef(null);


 const onAddDocuments = () => {
   hiddenFileInput.current.click();
 };


 const handleAddDocuments = (event) => {
   const uploadedFiles = Array.from(event.target.files);


   const files = uploadedFiles.map((file) => ({
     file
   }));


   append(files);


   hiddenFileInput.current.value = "";
 };


 return (
   <>
     <HiddenInput
       ref={hiddenFileInput}
       type="file"
       multiple
       onChange={handleAddDocuments}
     />


     <Box sx={{ m: 2, width: 300 }}>
       <Label>Documents</Label>


       {fields.map(({ documentId, file }, index) => (
         <div key={documentId}>
           <Controller
             control={control}
             name={`documents.${index}`}
             render={() => (
               <Grid container alignItems="center">
                 <Grid item xs={10}>
                   {file.name}
                 </Grid>


                 <Grid item xs={2}>
                   <IconButton
                     aria-label="Remove"
                     onClick={() => remove(index)}
                   >
                     <DeleteForeverIcon />
                   </IconButton>
                 </Grid>
               </Grid>
             )}
           />
         </div>
       ))}


       <Button variant="text" onClick={onAddDocuments}>
         Add documents
       </Button>
     </Box>
   </>
 );
};

The final result is а nice form with applied validation and the option to upload files.

Here’s the full code of the example: https://codesandbox.io/s/musing-villani-xdhobm. Of course, you are free to use whatever components you want, just keep in mind to set them in the register.

Overall, the React Hook Form library is an excellent choice if you want good-looking forms with great UX. The library is well-maintained and has detailed documentation.

Categories