Written by Nikola Đuza. Updated on . First published on June 2, 2023, in Product
React is one of the popular tools for building user interfaces and is a skill worth knowing. If you're building something with React, you will face the challenge of uploading files sooner or later. Do not worry; writing a file-uploading logic in React is straightforward, and this post will show you exactly how to do it.
We'll cover how to upload a single file, upload multiple files, and see how you can use a 3rd party library that does the heavy lifting for you. But first, let's dive into uploading a single file in React with fetch.
Then, we'll create a new component called SingleFileUploader in src/components/SingleFileUploader.tsx. For starters, we will show how to add a file picker in React and have the user interact with it. Here's the code:
import React,{ useState }from"react";constSingleFileUploader=()=>{const[file, setFile]=useState<File |null>(null);consthandleFileChange=(e: React.ChangeEvent<HTMLInputElement>)=>{if(e.target.files){setFile(e.target.files[0]);}};consthandleUpload=async()=>{// We will fill this out later};return(<><div><labelhtmlFor="file"className="sr-only">
Choose a file
</label><inputid="file"type="file"onChange={handleFileChange}/></div>{file &&(<section>
File details:
<ul><li>Name: {file.name}</li><li>Type: {file.type}</li><li>Size: {file.size} bytes</li></ul></section>)}{file &&<buttononClick={handleUpload}>Upload a file</button>}</>);};exportdefault SingleFileUploader;
Now, a user can pick a file they want to upload. When a user chooses a file, we show a summary (file name, file type, and file zie) of the selected file under the file input.
There's also a small class for the label element called sr-only that makes it accessible for screen readers but hidden for folks seeing the file input. That's a minor web accessibility touch-up you can read more about here.
But all in all, here's how the file input and the showing of details work.
File summary
Now, we have to write the logic inside the handleUpload function that will actually upload the selected file. For that, we're going to use fetch and FormData. Let's see what those are below.
FormData interface to easily append files to the fetch payload we'll send
Here's how those two will look in the code:
consthandleUpload=async()=>{if(file){console.log("Uploading file...");const formData =newFormData();
formData.append("file", file);try{// You can write the URL of your server or any other endpoint used for file uploadconst result =awaitfetch("https://httpbin.org/post",{
method:"POST",
body: formData,});const data =await result.json();console.log(data);}catch(error){console.error(error);}}};
Here, we first check whether the file is present so we have something to upload. After that, we initiate the new FormData object with new FormData() and assign it to formData. Now, formData allows us to append a file to it with formData.append(file). Then, we call fetch with the URL endpoint for uploading files. There's a random URL in the example, but in reality, it should be an endpoint you're working with (usually your backend or a 3rd party service).
Then, in the fetch call, we simply pass the formData object inside the body like so:
const result =awaitfetch("https://httpbin.org/post",{
method:"POST",
body: formData,});
The fetch and FormData work flawlessly together, and that's all you have to do to ensure a file is sent properly. When we tie the file input component and the new logic we wrote into one piece of code, here's how it will look:
import React,{ useState }from"react";constSingleFileUploader=()=>{const[file, setFile]=useState<File |null>(null);consthandleFileChange=(e: React.ChangeEvent<HTMLInputElement>)=>{if(e.target.files){setFile(e.target.files[0]);}};consthandleUpload=async()=>{if(file){console.log("Uploading file...");const formData =newFormData();
formData.append("file", file);try{// You can write the URL of your server or any other endpoint used for file uploadconst result =awaitfetch("https://httpbin.org/post",{
method:"POST",
body: formData,});const data =await result.json();console.log(data);}catch(error){console.error(error);}}};return(<><div><labelhtmlFor="file"className="sr-only">
Choose a file
</label><inputid="file"type="file"onChange={handleFileChange}/></div>{file &&(<section>
File details:
<ul><li>Name: {file.name}</li><li>Type: {file.type}</li><li>Size: {file.size} bytes</li></ul></section>)}{file &&<buttononClick={handleUpload}>Upload a file</button>}</>);};exportdefault SingleFileUploader;
And here's how it works:
Upload single file in React
We select a file, view its details and click "Upload a file" button. Then, after some time, in the browser's console, we can view the result of the file upload request meaning our file got uploaded successfully. To make things easier to follow through and avoid relying on the browser's console, we can make a simple section in the UI to show whether the upload started and if it was successful or not.
Now, we can close the browser console and view the upload state in the UI directly like so:
Single file upload in React with status indicator
Great, now that we covered the basics on how to show file input, selected file details, how to upload a single file in React - let's show how to upload multiple files.
The majority of the code will stay the same from the previous section where we show how to upload the single file. The only part that will change is the part where the user can choose multiple files to upload. For that purpose, we'll create a new component called MultipleFileUploader.tsx in src/components:
Now, we set files inside the component's state and have it as FileInput type instead of File type previously. With that, we get all the e.target.files and save it in the state. Then, in the handleUpload logic, we iterate through files with a simple trick append each file to formData with formData.append('files', file). The trick to iterate through FileList is to destructure it into an JavaScript array like so:
[...files].forEach(...)
Unfortunately, we have to do that because the FileList doesn't have the typical Array methods like map or forEach. Later, in the markup, we do the same trick when showing each file's data.
Awesome, we set up everything, and now this is how multi-file upload works in React:
Multiple files upload in React with status indicator
But, what if we can make that easier and if we try to use an already-baked solution? Let's try out Uploadcare's File Uploader for file upload in the next section.
Uploadcare made a brand new uploader that is fully customizable. The new uploader is built with Web Components and part of the Uploadcare Blocks components.
To get started, you need to install the Uploadcare Blocks with the following command:
npm install @uploadcare/blocks
And add types configuration to tsconfig.json in the root of the project:
There's a lot to unravel from the code above, but don't worry. We will do it gradually. First off, we need to import LR from @uploadcare/blocks and call registerBlocks on it. This is needed so we can register the components that are going to be used in this UploadcareUploader React component.
Then, we see this part of the code:
constUploadcareUploader=()=>{const dataOutputRef =useRef<LR.DataOutput>();const[files, setFiles]=useState<any[]>([]);const handleUploaderEvent =useCallback((e: CustomEvent<any>)=>{const{ data }= e.detail;setFiles(data);},[]);useEffect(()=>{const el = dataOutputRef.current;
el?.addEventListener("lr-data-output",
handleUploaderEvent as EventListenerOrEventListenerObject
);return()=>{
el?.removeEventListener("lr-data-output",
handleUploaderEvent as EventListenerOrEventListenerObject
);};},[handleUploaderEvent]);// ...};
There, we are setting a way to get the files from the Uploadcare File Uploader and set them inside the React component state. We create a ref, state for the files, and register events for when the file is uploaded and removed.
After that, we come to the markup part of the component.
Now we get to the fun part. First off, we wrap everything into a section HTML element (this can be some other element of your choice) and we set a CSS variable with our Uploadcare project API key. You can retrieve the key easily from the Uploadcare project dashboard. We used an environment variable VITE_UPLOADCARE_API_KEY to store it, but you can use your own solution for that. This step is important because later, we will configure the Uploadcare File Uploader through CSS 🤯
Then, we put two components provided by Uploadcare Blocks:
The lr-file-uploader-regular and lr-data-output. The lr-file-uploader-regular provides the actual UI for uploading files, and lr-data-output is there so we can track what gets uploaded and taken down by the user in the UI.
As you can notice, both of these elements have the class attribute and the "uploaderCfg" value. This is because Uploadcare Blocks are customized via CSS. We put the CSS in UploadcareUploader.css and imported it at the top. Here's how the CSS looks:
The --ctx-name is to make sure both lr-file-uploader-regular and lr-data-output are connected. Both elements need to have the same --ctx-name. Then, the --cfg-pubkey is the place where we put the Uploadcare project API key. You can hardcode it there, but we added it through a environment variables so it is not showing in the code. Vite supports .env files so there's an entry there like this VITE_UPLOADCARE_API_KEY=my_key. Then, in the code, we set it as a CSS variable --uploadcare-pubkey in the parent element and then read it as var(--uploadcare-pubkey) in CSS and set it for --cfg-pubkey so Uploadcare File Uploader can pick it up.
After that, we show the uploaded files from the files in the React component state. All files (in this case, images) are rendered in the gallery below the uploader.
To get a better idea of it all, let's see how it works below:
Uploadcare File Uploader
And that's it, Uploadcare will take care of the uploading for you and make it faster with its uploading network (it works like CDN). You also get the tracking of uploading of each file and the option to choose where you'd like to upload files from (from the device, from a link, local camera, Dropbox, Google Drive etc.)