Sometimes you may need to provide a page where users can upload files. It's quite easy if you only use HTML file input. However if you use a UI framework, you need different way to handle it. In addition, you may also want the upload page to support drag and drop functionality, so that users can select the files to be uploaded first on file explorer, drag the files and then drop them on the provided drop zone box. If you use ReactJS as the UI framework and you need to handle multiple files upload, you come to the right place. This tutorial shows you how to upload files, either single or multiple, using ReactJS and Node.js as the backend. We use ajax for sending the request to the server.
In this tutorial, I'll only focus on how to handle the request containing uploaded files in backend and how to create the React components as well as sending the request to the server. I assume you've already familiar with routing in Node.js, using template engine (such as Handlebars, pug (Jade), etc.), and of course the basic of ReactJS as well as how to compile your ReactJS code. To make it simple, I don't use Redux in this tutorial. But if you have been experienced with Redux and need to use it, it's quite easy to convert this non-Redux tutorial to Redux style.
Dependencies
Here is the dependenies we need for this tutorial
"multer": "~1.3.1"
"react": "~16.4.1",
"react-dom": "~16.4.1",
"react-dropzone": "~4.2.12"
You need to install those dependencies first.
Backend Code
Your application must be able to handle the request containing the uploaded files. The easiest way is using multer used as a middleware on the route(s) where you want to handle image upload. Here is the basic example of how to use multer as middleware.
src/routes/index.js
const multer = require('multer');
const fileUploadHandler = require('../src/file-upload/controllers/handlers/upload');
const upload = multer({ dest: '/tmp' });
router.post(
'/file-upload',
upload.any(),
fileUploadHandler,
);
The multer constructor supports the following key:
Key | Description |
dest or storage | The directory where the uploaded files will be stored |
fileFilter | A function for controlling which files are accepted |
limits | Limit the uploaded files |
preservePath | Use full path of files instead of base name only |
On the code above, we use upload.any(). The available options are:
Key | Description |
.single(fieldname) | Accept a single file |
.array(fieldname[, maxCount]) | Accept array of files and you can set the maximum number of files |
.fields(fields) | Accept files with specified fields, you can set maxCount for each fields. Example:[ { name: 'mainphoto', maxCount: 1 }, { name: 'otherphotos', maxCount: 10 } ] |
.none() | Accept only text fields |
.any() | Accept all files |
Here is the upload handler
src/file-upload/controllers/handlers/upload.js
module.exports = (req, res) => {
for (let i = 0; i < req.files.length; i += 1) {
console.log(`File ${req.files[i].originalname} uploaded to ${req.files[i].path}`);
}
return res.status(200).send({ success: true });
};
Frontend Code
Instead of making components from scratch, we're going to use react-dropzone module. It's a quite popular library, very easy to use and most importantly, it works. Using react-dropzone is quite easy. You only need to import and create a new component of it inside render() function. It accepts the following props:
Props | Type | Default | Description |
---|---|---|---|
disableClick | bool | false | If true, clicking on the dropzone won't open a file dialog |
disabled | bool | false | If true, the dropzone will be disabled |
disablePreview | bool | false | If true, preview generation will be disabled |
preventDropOnDocument | bool | true | If false, allow dropped items to take over the current browser window |
inputProps | object | Pass additional attributes to the <input type="file" /> tag | |
multiple | bool | true | Allow dropping multiple files |
name | string | name attribute for the input tag | |
maxSize | number | Infinity | Maximum file size (in bytes) |
minSize | number | 0 | Minimum file size (in bytes) |
activeClassName | string | Applied className when drag is active | |
acceptClassName | string | Applied className when drop will be accepted | |
rejectClassName | string | Applied className when drop will be rejected | |
disabledClassName | string | Applied className when dropzone is disabled | |
style | object | CSS styles to apply | |
activeStyle | object | Applied CSS styles when drag is active | |
acceptStyle | object | Applied CSS styles when drop will be accepted | |
rejectStyle | object | Applied CSS styles when drop will be rejected | |
disabledStyle | object | Applied CSS styles when dropzone is disabled | |
onClick() | func | onClick callback. Arguments event: Event | |
onDrop(acceptedFiles, rejectedFiles) | func | Will be triggered when on drop events | |
onDropAccepted(acceptedFiles) | func | Will be triggered when on drop accepted events | |
onDropRejected(rejectedFiles) | func | Will be triggered when on drop rejected events | |
onDragStart() | func | Will be triggered when on drag start events | |
onDragEnter() | func | Will be triggered when on drag enter events | |
onDragOver() | func | Will be triggered when on drag over events | |
onDragLeave() | func | Will be triggered when on drag leave events | |
onFileDialogCancel() | func | Will be triggered when the user closes file dialog |
And here is the basic example:
src/file-upload/views/containers/FileUploadContainer.jsx
import Dropzone from 'react-dropzone';
import React, { Component } from 'react';
class FileUploadContainer extends Component {
constructor() {
super();
this.onImageDrop = this.onImageDrop.bind(this);
}
onImageDrop(acceptedFiles) {
const data = new FormData();
for (let i = 0; i < acceptedFiles.length; i += 1) {
data.append('file', acceptedFiles[i]);
}
$.ajax({
url: '/file-upload',
data,
processData: false,
contentType: false,
method: 'POST',
dataType: 'json',
success: (response) => {
if (response.success) {
alert('success');
} else {
alert('failed');
}
},
error: (jqXHR) => {
const res = jqXHR.responseJSON;
alert('error: ' + JSON.stringify(res));
},
});
}
render() {
return (
<div>
<Dropzone
multiple
onDrop={this.onImageDrop}
>
<p>Drop images or click to select a file to upload</p>
</Dropzone>
</div>
);
}
}
export default FileUploadContainer;
That's all about how to upload multiple files using React.js and Node.js, by utilizing react-dropzone and multer.