Published in · 7 min read · Jun 19, 2022
Today, a lot of websites have an option to upload files, mostly through forms. If you want to add such functionality to your website, you’ve come to the right place. In this post, I am going to show you how to implement multiple file uploads in React.
For this post, I assume you have a basic knowledge of React. If not, then head over to React Docs to get started.
First, create the React app with the following command.
create-react-app multiple-file-upload
I have used Bootstrap in the project, so add this CDN to index.html file.
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
Alternatively, you can download the bootstrap source files from here.
First, create an input
of type file that can accept multiple uploads. If you want to allow only certain file types, you can use the accept
attribute and specify the file types you want to allow.
<input id='fileUpload' type='file' multiple accept='application/pdf, image/png'/>
But it would be better to have a button with your own styles and your own way of displaying files. So, add the property display: none
to the input and have a label with the attribute htmlFor
(React alternative for for
attribute in HTML) set to the id of the input. With this, the label
is bound to the input
and is able to replicate its functionality.
<label htmlFor='fileUpload'> <a className='btn btn-primary'> Upload Files </a></label>
Inside this label
, you can display anything you want, in this case, I have used a simple button. Note that I have used a
instead of button
and applied bootstrap classes to it.
Now, the reason to hide the input
and have your own way of displaying files is that the default functionality has some limitations. While uploading multiple files, only the number of files is visible and when you try to upload again, the uploaded files get replaced.
So far, you have a static upload button where you can upload multiple files with the above-mentioned limitations.
In some cases, this wouldn’t be a problem, but what if a user cannot select all the files at once? What if all the files are in different folders?
With the above limitations, the user would first have to bring all the files into the same folder and upload them all at once. This would be tedious and our job is to make things easier for the end-user. Let’s see how we can do that.
Handle the file upload event
To handle the file upload event, add an onChange
attribute to the input
that takes a callback function handleFileEvent
.
const handleFileEvent = (e) => { --- HANDLE FILE UPLOAD EVENT HERE ---}
This function takes an event object as an argument containing various properties of the event. It also contains the files that were uploaded in the event. These files can be accessed by using e.target.files
.
The files are stored in the form of an array-like object.
Store the uploaded files as state
Create a state uploadedFiles
to store the list of currently uploaded files. Initially, it is empty.
const [uploadedFiles, setUploadedFiles] = useState([])
Our state uploadedFiles
is an array but event.target.files
is an object so you need to convert it to an array. There is a method for converting array-like objects to an array. Do this inside your handleFileEvent
method.
const chosenFiles = Array.prototype.slice.call(e.target.files)handleUploadFiles(chosenFiles);
chosenFiles
contains the files that are being uploaded in the current event.
Now, add these files to the state inside the handleUploadFiles
method that takes the chosen files as an argument.
const handleUploadFiles = files => { --- ADD FILES TO STATE ---}
First, create a copy of the state array and add all the currently chosen files to that array. I have used the some()
method instead of forEach
for a reason I’ll explain soon.
const uploaded = [...uploadedFiles];files.some((file) => { uploaded.push(file);})
When the loop ends, update the state array. The state update is done at the end since it is an asynchronous operation.
setUploadedFiles(uploaded);
Check if the file already exists
You don’t want users to upload the exact same file multiple times. So, add the following condition while adding the files to the uploaded
list.
if (uploaded.findIndex((f) => f.name === file.name) === -1) { uploaded.push(file);}
The findIndex
method searches for the file inside uploaded
with the same name as the one being currently added.
Limit the number of files to be uploaded
Sometimes, you could have a scenario where you need to limit the number of files a user can upload. Checking the number of selected files can be done during the uploading event or while submitting the form. I am going to show you how you can do it during the uploading event.
First, create a state variable that indicates if the user has reached the file upload limit. The default value is false
.
const [fileLimit, setFileLimit] = useState(false);
Now, inside the handleUploadFiles
function create a local variable limitExceeded
and initialize it to false.
While pushing files to the uploaded
array, add the following checks.
if (uploaded.length === MAX_COUNT) setFileLimit(true);if (uploaded.length > MAX_COUNT) { --- WHEN THE LIMIT IS EXCEEDED ---}
For now, the maximum limit is MAX_COUNT = 5
. When the number of uploaded files reaches this limit, update the state accordingly. But this is not enough, you also need to add the condition for limit-exceeded as the user can still upload multiple files at any stage.
The following logic is for a situation when the number of already uploaded files and the files currently chosen goes above the limit.
setFileLimit(false);limitExceeded = true;return true;
If the limit is exceeded, do not allow the user to add a single file. Since we are back to the previous state of limit not being reached, set fileLimit
to false.
Now, the reason I have used some
instead of forEach
is that I do not want to allow the user to upload the selected files if their number is exceeding the limit. To do this, I needed to break the loop at this point. Since breaking out of a forEach
loop is almost impossible, I have used the some
method.
some()
method is actually used to check whether an element in an array meets a certain condition. If it does, then it returns true and breaks the loop. So, I have returned true
from the function to break the loop.
If you could think of any other logic to prevent uploading excess files, comment down below.
While updating the uploadedFiles
state, check if the limit was exceeded.
if (!limitExceeded) setUploadedFiles(uploaded)
To disable the button if the limit is reached, set disabled = {fileLimit}
for the input
and add a disabled
class to the button.
<a className={`btn btn-primary ${!fileLimit ? '' : 'disabled' } `}>Upload Files
</a>
This part is pretty simple. Just display the names of the files after the upload button.
<div className="uploaded-files-list"> {uploadedFiles.map(file => ( <div> {file.name} </div> ))} </div>
You can display the files in various ways by using icons and styled text. For now, I have simply displayed a list of file names. Also, I haven’t added a key prop to the div
element. React gives a warning so make sure to add it while rendering a list of elements.
Finally, this is what the App component looks like.
You can find this project on GitHub. The above implementation is a modification of this one. Do check it out.
While implementing forms, your website may also need to accept files from users. Sometimes, the default functionality is unable to satisfy your requirements. So, you need to add your own functionality.
In this post, I have shown you how to implement the same in React. I have explained each and every step of the implementation. I hope this helps you in your future projects. Of course, there might be plenty of other ways to implement this functionality. Comment down below if any improvements can be made to this implementation.
If you are unable to understand the content or find the explanation unsatisfactory, comment your thoughts below. New ideas are always appreciated! Give a few claps if you liked this post. Subscribe and follow me for weekly content. Reach out to me on Twitter if you want to discuss anything. Till then, Goodbye!!