How to Upload Files With React

Learn to build a custom file input that selects files using the HTML input element and upload them as FormData in React.

· 6 min read
How to Upload Files With React
📣
If you're looking for a simple drop-in file upload component, check out Uppy.

Uploading a single file

To upload a single file in React, you need to set the content-type and content-length headers and provide the file contents as the request body:

import { ChangeEvent, useState } from 'react';

function FileUploadSingle() {
  const [file, setFile] = useState<File>();

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      setFile(e.target.files[0]);
    }
  };

  const handleUploadClick = () => {
    if (!file) {
      return;
    }

    // 👇 Uploading the file using the fetch API to the server
    fetch('https://httpbin.org/post', {
      method: 'POST',
      body: file,
      // 👇 Set headers manually for single file upload
      headers: {
        'content-type': file.type,
        'content-length': `${file.size}`, // 👈 Headers need to be a string
      },
    })
      .then((res) => res.json())
      .then((data) => console.log(data))
      .catch((err) => console.error(err));
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />

      <div>{file && `${file.name} - ${file.type}`}</div>

      <button onClick={handleUploadClick}>Upload</button>
    </div>
  );
}

export default FileUploadSingle;
  1. First, we add an input element with type="file" attribute.
  2. We can store the selected file in React component state, after receiving it from the onChange event. Since we're only able to select a single file, we can get it from the files array on the input: e.target.files[0].
  3. We can upload the file using the Fetch API. We need to set the set body to the file we received from the input and the headers: content-type to the file type and the content-length to file.size. Note that headers must be string values.

I used a httpbin.org API that accepts file uploads and responds with a copy of the request you send. Here's the result:

Demo of the single file upload.

Uploading files usually requires some work on the backend to accept, store, and serve those files upon request. If you'd like to learn that and more, I highly recommend educative.io courses.

Here's one that will help you master full-stack development with React:

Building Full-Stack Web Applications With Node.js and React - Learn Interactively
Node.js is a popular JavaScript runtime environment used to create server-side applications. It is an ideal tool for building robust, full-stack web applications with React. This course is an introduction to web development leveraging these two popular technologies. You’ll learn server-side applica…

Uploading multiple files

To upload multiple files from input element in React, you need to use the FormData JavaScript API and encode the request as multipart/form-data.

import { ChangeEvent, useState } from 'react';

function FileUploadMultiple() {
  const [fileList, setFileList] = useState<FileList | null>(null);

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    setFileList(e.target.files);
  };

  const handleUploadClick = () => {
    if (!fileList) {
      return;
    }

    // 👇 Create new FormData object and append files
    const data = new FormData();
    files.forEach((file, i) => {
      data.append(`file-${i}`, file, file.name);
    });

    // 👇 Uploading the files using the fetch API to the server
    fetch('https://httpbin.org/post', {
      method: 'POST',
      body: data,
    })
      .then((res) => res.json())
      .then((data) => console.log(data))
      .catch((err) => console.error(err));
  };

  // 👇 files is not an array, but it's iterable, spread to get an array of files
  const files = fileList ? [...fileList] : [];

  return (
    <div>
      <input type="file" onChange={handleFileChange} multiple />

      <ul>
        {files.map((file, i) => (
          <li key={i}>
            {file.name} - {file.type}
          </li>
        ))}
      </ul>

      <button onClick={handleUploadClick}>Upload</button>
    </div>
  );
}

export default FileUploadMultiple;
Uploading multiple files using FormData in ReactJS.

The first difference from our single file upload example is the addition of multiple attribute on the input element.

Instead of storing a single file in the React component state, we save the whole FileList in state

📣
Note that the FileList is not an array, so we can't use regular array methods like map or forEach. However, we can still access the members by index fileList[0], loop through the files using for..of or spread them. 

To upload multiple files:

  1. Create a FormData object: const data = new FormData();
  2. Append each file you want to upload using FormData.append() - it accepts a form field name, the file, and a file name as parameters.
  3. Using the Fetch API, upload the files by setting form data as body. Note that when you use form data you don't need to set headers manually. It's taken care of by fetch API.

Here's what it looks like:

Demo of multi-file upload.

Customizing the file input

The default input element doesn't offer much in terms of styling it. To create a custom file upload input in React, you will need to hide the native file upload input and trigger the click events on the input using refs:

import { ChangeEvent, useRef, useState } from 'react';

function CustomFileInput() {
  const [file, setFile] = useState<File>();
  const inputRef = useRef<HTMLInputElement | null>(null);

  const handleUploadClick = () => {
    // 👇 We redirect the click event onto the hidden input element
    inputRef.current?.click();
  };

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files) {
      return;
    }

    setFile(e.target.files[0]);

    // 🚩 do the file upload here normally...
  };

  return (
    <div>
      <div>Upload a file:</div>

      {/* 👇 Our custom button to select and upload a file */}
      <button onClick={handleUploadClick}>
        {file ? `${file.name}` : 'Click to select'}
      </button>

      {/* 👇 Notice the `display: hidden` on the input */}
      <input
        type="file"
        ref={inputRef}
        onChange={handleFileChange}
        style={{ display: 'none' }}
      />
    </div>
  );
}
Creating a custom file input component in React.

Only the input element with file type can open the files for selection in the browser. To upload a file when clicking the custom button, we need to trigger the click() event on the input: inputRef.current?.click();

If we don't want to show the native input, we can hide it by adding the display: none CSS property in it's style attribute or by applying a CSS class that sets the display property to none (e.g. in Tailwind, the class name is hidden).

From then on, you can save the selected files in state or upload them immediately. You now have full customization of the file upload input:

Demo of a custom file input.

Conclusion

In React file upload is implemented by using the HTML input element and some JavaScript to make a POST/PUT request to the server with the FormData that contains the selected files.

Your server will then need to process multipart form data to accept file uploads. You can use Multer in Node.js to implement that or upload the files directly to Amazon S3 if that's what you want.

You can find the code used in the examples in my GitHub repo.


Now that you know how to handle files on the front end you should also learn to handle them on the backend, I recommend this full-stack course on educative.io to help you do that:

Building Full-Stack Web Applications With Node.js and React - Learn Interactively
Node.js is a popular JavaScript runtime environment used to create server-side applications. It is an ideal tool for building robust, full-stack web applications with React. This course is an introduction to web development leveraging these two popular technologies. You’ll learn server-side applica…