Forms are essential to websites and web applications. They represent a section where users can add input to be validated and submitted.
Validating the data the user passes into the form, handling the form state, and showing errors are important parts of a web developer’s job.
This article will cover building a robust form that accepts multiple file types and tends the response to an express server.
We will work with the React-hook-form library. It’s a performant library that makes it easier to validate forms in React. You only need to create a small amount of code because it has no other dependencies and is simple to use, requiring less code than other form libraries.
This article assumes a good grasp of Node, Express, and React.
Creating an Express Server
Let’s get started by creating our express server, which will be in charge of receiving the uploaded files and then sending back a response. Navigate to the code editor of your choice and run the command below to create a package.json file:
npm init -y
Next, we will install express, cors for allowing the upload of files and express-fileupload, which is a simple middleware for creating and managing the downloaded file paths with the command below:
npm i express cors express-fileupload
The next step is to create a server.js file that will host the server code.
First, we will include the packages we will need in our server.
const express = require('express');
const fileupload = require('express-fileupload');
const cors = require('cors');
Next, we create an Express application:
const app = express();
Next, we create the controllers which our application will use:
app.use(
fileupload({
createParentPath: true,
})
);
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
- The createParentPath is used to create directory paths for our uploaded files.
- The .urlencoded in line 8 indicates that we are parsing URL-encoded data for the front end of our application.
Next is to create a route for POST requests for the form submission:
app.post('/files', async (req, res) => {
try {
if (!req.files) {
res.send({
status: 'failed',
message: 'No file',
});
} else {
let file = req.files.file;
console.log(req.files);
file.mv('./uploads/' + file.name);
res.send({
status: 'success',
message: 'File successfully uploaded',
data: {
name: file.name,
mimetype: file.mimetype,
size: file.size,
},
});
}
} catch (err) {
res.status(500).send(err);
}
});
const port = process.env.PORT || 3001;
app.listen(port, () => console.log(`Server started on port ${port}`));
With that, we are done creating the server for our application.
Creating a React Application
Let’s create the front end of the application. Run the command below in another terminal to bootstrap a React application.
With npx:
npx create-react-app react-form
Yarn:
yarn create react-app react-form
Next, run the command below to install React Hook Form:
npm install react-hook-form
In the App.js file, import the react-hook-form library:
import { useForm } from 'react-hook-form';
We will pull out the properties we will be using from the useForm hook:
const {
register,
handleSubmit,
reset,
formState,
formState: { errors },
} = useForm();
- The register properties will be used to register your form components thereby making them available for validation and submission
- The handleSubmit properties is for handling the submission of our form
- The reset properties will be used to clear our form after submission.
- The formState property is used for checking when the form is been submitted
- The formState: { errors } property is used for form validation and showing errors.
Next, let’s create our form
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="Title">Title: </label>
<input
defaultValue="test"
{...register('example', { required: true })}
/>
{errors.example && <p>This field is required</p>}
<br />
<br />
<br />
<label htmlFor="status">Status: </label>
<input {...register('exampleRequired', { required: true })} />
{errors.exampleRequired && <p>This field is required</p>}
<br />
<br />
<label htmlFor="Image">Image: </label>
<input
type="file"
{...register('file', { required: true })}
onChange={onChangePicture}
/>
{errors.file && <p>Please select an image</p>}
<br />
<br />
<img className="image" src={picture && picture} alt="" />
<br />
<br />
<br />
<br />
<input type="submit" />
</form>
</div>
);
In the code above, we have an input form that can accept files. Using the properties from the React Hook Form package, we have added validation and error handling to our form. Next, we have an img tag that will show the selected image preview. When we start the server by running yarn start, we don’t see the image preview when we select the image that we want.
Creating the Functions for Image Preview and Handling Form Submission
To create the functionality for previewing the selected images, we need to import useState to manage the picture state and then create a function for it below:
function App() {
const [picture, setPicture] = useState(null);
const onChangePicture = (e) => {
setPicture(URL.createObjectURL(e.target.files[0]));
};
We created a function to show the image preview in the code block above by setting the picture state to the URL.createObjectURL of the selected file. You can read more about the URL.createObjectURL here. With that done, we see an image preview when we select an image.
Now, lets create the function for handling the form submission:
const onSubmit = async (data) => {
const formData = new FormData();
formData.append('file', data.file[0]);
const res = await fetch('http://localhost:3001/files', {
method: 'POST',
body: formData,
}).then((res) => res.json());
console.log(res);
alert(JSON.stringify(`${res.message}, status: ${res.status}`));
setPicture(null);
};
In the functionality above, we grab the selected data from the form and then append it to a new FormData(). We will make a post request to the created express server with the appended FormData() as the body, and then we console.log the request and then set the picture state to null for a better user experience.
We will select an image to be sure that our app works fine and fill out our form, but then we get an error when we submit, and this is because we haven’t started up the Express server. To set that up, Navigate to the server file on your code editor and run the command below to start up the server:
node server
This will return a server started on port 3001
Now when you fill out the form and click on submit, you get the data logged to the console on the Express server. We also have a robust form with error handling and validation which can accept different file types. Below is an image showing validation on our form:
Now when we fill the form and select our file, we can see the image preview and when we submit, we get the successful response from the Express server, which shows as an alert. Below is an image showing this:
Uploading Multiple Files With React Hook Form and Multer
We have shown how to upload a single file. This section will cover how to preview multiple files before sending, upload the previewed files and then receive the uploaded files in our express server.
Let’s start with the server, we will install the Multer package for handling multiple file uploads.
Make sure that you are in the backend directory and run the command below in your terminal to install multer:
npm install --save multer
We will include multer into our application and then create a way to store the files coming from the frontend using one of the ways provided by multer which is the diskStorage approach.
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads');
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + '-' + Date.now() + path.extname(file.originalname)
);
},
});
const upload = multer({ storage: storage });
The diskStorage approach takes in two arguments.
The first argument is the destination in which we want the files to be saved which we indicated as the uploads folder while the second argument is the name we want to use to save our file.
In line 15, the defined storage option is passed to the real storage object.
Next, we will create a function that will hold the same name as the input field on the front end where we are sending the files from. We will then pass this function as one of the arguments to the app.post route as seen in the code block below:
const uploadMultiple = upload.fields([{ name: 'file1' }]);
app.post('/files', uploadMultiple, function (req, res, next) {
if (req.files) {
console.log(req.files);
console.log('files uploaded');
}
});
With that, we are done with the server-side code. Let’s navigate to the frontend folder, enable our input form to accept multiple files, and create the function for previewing selected images.
All we need to do to make our input field accept multiple files is to write the multiple tag in the input as shown below:
<input
type="file"
name="file1"
multiple
{...register('file', { required: true })}
onChange={changeMultipleFiles}
/>
As you can see, we are putting the multiple tags in our input and also making sure the name of our input matches that of the name in the uploadMultiple function we created on the server.
Next, create a state which will hold the multiple images and set it to and empty array then create a changeMultipleFiles function and paste the code below into it
const [multipleImages, setMultipleImages] = useState([]);
// Functions to preview multiple images
const changeMultipleFiles = (e) => {
if (e.target.files) {
const imageArray = Array.from(e.target.files).map((file) =>
URL.createObjectURL(file)
);
setMultipleImages((prevImages) => prevImages.concat(imageArray));
}
};
const render = (data) => {
return data.map((image) => {
return <img className="image" src={image} alt="" key={image} />;
});
};
In the code above, we create a function where we map over the selected files and then passed the single file to the URL.createObjectURL method. Then we set the array state to the function.
Next, we created a function for rendering our images where we mapped over the passed in data, and ten return our image. We will put our render function with the multipleImages state even below the input field as shown below:
{/* input tag for multiple images */}
<input
type="file"
name="file1"
multiple
{...register('file', { required: true })}
onChange={changeMultipleFiles}
/>
{/* error handling with React Hook Form */}
{errors.file && <p className="error">Please select an image</p>}
{/* The render function with the multiple image state */}
{render(multipleImages)}
With that, we see the preview of our selected images when we select them as shown in the image below:
Paste the code below into our function for handling the form submission:
const onSubmit = (data) => {
setLoading(true);
const formData = new FormData();
for (const key of Object.keys(multipleImages)) {
formData.append('file1', data.file[key]);
}
fetch('http://localhost:3001/files', {
method: 'POST',
body: formData,
}).then((res) => console.log(res));
setMultipleImages([]);
setLoading(false);
};
In the code block above, we map through the multipleImages array and then append each file to a newly created FormData(). We then make a post request to the created express server with the appended FormData() as the body and then we console.log the request and then set the multipleImages array state back to empty for a better user experience.
We can see the files we sent to the server which will be shown on the terminal as in the image below:
Conclusion
This article has shown how to build a robust form with various capabilities such as error handling and verification using the React Hook Form. We also learned how to preview images using the URL.createObjectURL method. This article also showed how to build an Express server for accepting file uploads. We also learned how to preview multiple images and also how to accept multiple files with the Multer library.