React File Upload Tutorial: From Scratch and with Filestack (2026)

Posted on | Last updated on

To upload files in React: capture the file with <input type="file"> and an onChange handler storing it in state, wrap it in a FormData object, and POST it to your server with fetch or Axios using multipart/form-data, then handle progress, success, and errors in state. That takes ~30 lines plus a backend endpoint. The faster path is the filestack-react SDK: install it, render the picker with your API key, and you get uploads with progress, chunked retry for large files, drag-and-drop, and a CDN URL back, no backend to build. Both approaches, with complete working code, are below.

Key Takeaways

  • A React file upload has four parts: file selection (input type="file" + state), transmission (FormData + POST with multipart/form-data), feedback (progress, preview), and error handling.
  • FormData is mandatory for sending binary file data; with fetch, don’t set the Content-Type header manually; the browser sets the multipart boundary for you.
  • Always validate file type and size in the browser before uploading, and re-validate on the server; client-side checks are UX, not security.
  • Show upload progress with Axios’s onUploadProgress (or XHR events); users abandon silent uploads.
  • For production apps, the filestack-react SDK removes the backend entirely: chunked resumable uploads, drag-and-drop, cloud sources, and a CDN URL in a few lines.
  • The tutorial works in any modern React setup: Vite, Next.js, or an existing app; Create React App was deprecated in February 2025 and is no longer recommended.

Two Ways to Upload Files in React

Approach Effort You get Best for
From scratch (input + FormData + Axios/fetch) ~30 lines + your own backend endpoint Full control; you build progress, retries, storage Learning; custom pipelines
filestack-react SDK npm install + ~10 lines, no backend Picker UI, drag & drop, chunked resumable uploads, 20+ sources, CDN URL, transformations Shipping production uploads fast

Understanding the File Upload Process in React:

Uploading files in a React app, such as images, documents, or any other file types, typically follows a structured approach:

  1. User File Selection: The journey begins with allowing the user to select a file. In React, this is commonly achieved by utilizing the <input> element with its type attribute set to “file”. This offers a user-friendly interface for file selection. When a file is chosen, it’s important to have an event handler in place. This event handler listens to any changes or interactions with the file input and updates the application’s state with the selected file’s information.
  2. Server Communication: Once the file information is captured and stored in the application’s state, the next pivotal step is sending it over to a server. This could be for processing, storage, or any other backend operation. Tools like axios or the native fetch API are frequently employed to handle this communication. They aid in making asynchronous HTTP requests to servers. It’s crucial to wrap the selected file in a FormData object, ensuring the data is properly formatted for transmission.
  3. Feedback & Response Handling: Upon initiating communication with the server, always anticipate two outcomes: success or failure. Implementing feedback mechanisms like success messages, error alerts, or even displaying the uploaded file helps improve user experience. It provides assurance that their action (file upload) was successful or gives clarity if something went amiss.
  4. Error Handling: The digital realm isn’t always predictable. Issues might arise during the upload process, be it network glitches, file format mismatches, or server-side errors. Being prepared with a robust error-handling mechanism not only aids in troubleshooting but also ensures users aren’t left in the dark. Informative error messages and alternative solutions can steer users in the right direction.
  5. External Libraries and Tools: While React provides a solid foundation, sometimes external libraries or tools can expedite the development process. Tools like axios simplify HTTP communications. Moreover, services like Filestack offer out-of-the-box file uploading solutions, saving development time.

By adhering to this structured approach, developers can ensure a smooth and efficient file upload process in their React applications, enhancing both functionality and user satisfaction.

Now let’s dive in to the nitty gritty details.

We’re starting with a freshly created react app with the default content removed.

import './App.css';

function App() {
  return (
    <div className="App">

    </div>
  );
}

export default App;

The first thing we’ll do is create a simple form where our user can choose what file to upload.

import './App.css';
function App() {
  return (
    <div className="App">
        <form>
          <h1>React File Upload</h1>
          <input type="file" />
          <button type="submit">Upload</button>
        </form>
    </div>
  );
}

export default App;

Next, we’ll create a state variable, add an onChange event handler to the input element, and create a handleChange function to keep track of what file our user chose to upload.

import './App.css';
import React, { useState } from 'react';

function App() {

  const [file, setFile] = useState()

  function handleChange(event) {
    setFile(event.target.files[0])
  }

  return (
    <div className="App">
        <form>
          <h1>React File Upload</h1>
          <input type="file" onChange={handleChange}/>
          <button type="submit">Upload</button>
        </form>
    </div>
  );
}

export default App;

Now that we know what file our user chose to upload, we’ll add axios for making http requests, an onSubmit event handler to the form, and a handleSubmit function to upload the file using a http POST request.

import './App.css';
import React, { useState } from 'react';
import axios from 'axios';

function App() {

  const [file, setFile] = useState()

  function handleChange(event) {
    setFile(event.target.files[0])
  }
  
  function handleSubmit(event) {
    event.preventDefault()
    const url = 'http://localhost:3000/uploadFile';
    const formData = new FormData();
    formData.append('file', file);
    formData.append('fileName', file.name);
    const config = {
      headers: {
        'content-type': 'multipart/form-data',
      },
    };
    axios.post(url, formData, config).then((response) => {
      console.log(response.data);
    });

  }

  return (
    <div className="App">
        <form onSubmit={handleSubmit}>
          <h1>React File Upload</h1>
          <input type="file" onChange={handleChange}/>
          <button type="submit">Upload</button>
        </form>
    </div>
  );
}

export default App;

This is the critical step when enabling file uploads using React. We’ve created a config object to specify a ‘content-type’ header for our http request. In order to upload files, the ‘content-type’ header must be set to ‘multipart/form-data’.

new FormData() creates a new empty formData object that we send as the payload in our POST request. Our POST request assumes there is an API endpoint on our backend server at http://localhost:3000/uploadFile.

If you use fetch instead of Axios, pass the FormData object as the body and do not set the Content-Type header yourself; the browser must generate the multipart boundary.

Validating Files Before Upload

Never send a file the server will reject. Check type and size the moment the user selects a file:

const MAX_SIZE = 10 * 1024 * 1024; // 10 MB
const ALLOWED = ['image/jpeg', 'image/png', 'application/pdf'];

function handleChange(event) {
  const selected = event.target.files[0];
  if (!selected) return;
  if (!ALLOWED.includes(selected.type)) {
    setError('Only JPG, PNG, or PDF files are allowed.');
    return;
  }
  if (selected.size > MAX_SIZE) {
    setError('File must be under 10 MB.');
    return;
  }
  setError(null);
  setFile(selected);
}

Client-side validation is for user experience; it is trivially bypassable, so always re-validate type and size on the server as well.

Uploading Multiple Files

In many real-world applications, there’s a need for users to upload more than one file at a time. Let’s enhance our React app to support multiple file uploads.

import './App.css';
import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [files, setFiles] = useState([]);
  const [uploadedFiles, setUploadedFiles] = useState([]);

  function handleMultipleChange(event) {
    setFiles([...event.target.files]);
  }

  function handleMultipleSubmit(event) {
    event.preventDefault();
    const url = 'http://localhost:3000/uploadFiles';
    const formData = new FormData();
    files.forEach((file, index) => {
      formData.append(`file${index}`, file);
    });

    const config = {
      headers: {
        'content-type': 'multipart/form-data',
      },
    };

    axios.post(url, formData, config)
      .then((response) => {
        console.log(response.data);
        setUploadedFiles(response.data.files);
      })
      .catch((error) => {
        console.error("Error uploading files: ", error);
      });
  }

  return (
    <div className="App">
      <form onSubmit={handleMultipleSubmit}>
        <h1>React Multiple File Upload</h1>
        <input type="file" multiple onChange={handleMultipleChange} />
        <button type="submit">Upload</button>
      </form>
      {uploadedFiles.map((file, index) => (
        <img key={index} src={file} alt={`Uploaded content ${index}`} />
      ))}
    </div>
  );
}

export default App;

In this snippet, the input tag now has the multiple attribute, allowing users to select multiple files. We’re iterating over the selected files, adding each one to our FormData object, and then displaying each uploaded file in the app.

File Upload Progress

Another enhancement is to provide users with feedback on the progress of their file upload.

import './App.css';
import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [file, setFile] = useState();
  const [uploadProgress, setUploadProgress] = useState(0);

  function handleChange(event) {
    setFile(event.target.files[0]);
  }

  function handleSubmit(event) {
    event.preventDefault();
    const url = 'http://localhost:3000/uploadFile';
    const formData = new FormData();
    formData.append('file', file);

    const config = {
      headers: {
        'content-type': 'multipart/form-data',
      },
      onUploadProgress: function(progressEvent) {
        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
        setUploadProgress(percentCompleted);
      }
    };

    axios.post(url, formData, config)
      .then((response) => {
        console.log(response.data);
      })
      .catch((error) => {
        console.error("Error uploading file: ", error);
      });
  }

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <h1>React File Upload with Progress</h1>
        <input type="file" onChange={handleChange} />
        <button type="submit">Upload</button>
        <progress value={uploadProgress} max="100"></progress>
      </form>
    </div>
  );
}

export default App;

Here, we’ve added an onUploadProgress function to our Axios config. This function updates our uploadProgress state variable with the current percentage of the upload. We display this percentage using the HTML5 progress element.

For very large files, a single POST is fragile: one network blip fails the whole upload. Split files into chunks and retry failed chunks, or use an uploader that does this for you: the Filestack section below gets chunked, resumable uploads by default.

These enhancements not only provide a better user experience but also cater to more practical use cases in file uploading scenarios. Now let’s move forward to displaying the uploaded file.

Displaying the Uploaded File in the React App

After successfully uploading a file, it’s often beneficial for the user to get feedback and see the file they just uploaded. In this section, we’ll update the state with the uploaded file’s URL and display the file in our React app.

import './App.css';
import React, { useState } from 'react';
import axios from 'axios';

function App() {

  const [file, setFile] = useState()
  const [uploadedFileURL, setUploadedFileURL] = useState(null)

  function handleChange(event) {
    setFile(event.target.files[0])
  }

  function handleSubmit(event) {
    event.preventDefault()
    const url = 'http://localhost:3000/uploadFile';
    const formData = new FormData();
    formData.append('file', file);
    formData.append('fileName', file.name);
    const config = {
      headers: {
        'content-type': 'multipart/form-data',
      },
    };
    axios.post(url, formData, config).then((response) => {
      setUploadedFileURL(response.data.fileUrl);
    });
  }

  return (
    <div className="App">
        <form onSubmit={handleSubmit}>
          <h1>React File Upload</h1>
          <input type="file" onChange={handleChange}/>
          <button type="submit">Upload</button>
        </form>
        {uploadedFileURL && <img src={uploadedFileURL} alt="Uploaded content"/>}
    </div>
  );
}

export default App;

 

In this snippet, we added a new state variable uploadedFileURL which holds the URL of the uploaded file. After we get a successful response from the server, we update this state variable with the file’s URL which we then use to display the image in our application.

 

Handling React File Upload Errors

It’s good practice to handle potential errors that might occur during the file upload process. Let’s add some error handling to our handleSubmit function:

import './App.css';
import React, { useState } from 'react';
import axios from 'axios';

function App() {

  const [file, setFile] = useState();
  const [uploadedFile, setUploadedFile] = useState();
  const [error, setError] = useState();

  function handleChange(event) {
    setFile(event.target.files[0]);
  }
  
  function handleSubmit(event) {
    event.preventDefault();
    const url = 'http://localhost:3000/uploadFile';
    const formData = new FormData();
    formData.append('file', file);
    formData.append('fileName', file.name);
    const config = {
      headers: {
        'content-type': 'multipart/form-data',
      },
    };
    axios.post(url, formData, config)
      .then((response) => {
        console.log(response.data);
        setUploadedFile(response.data.file);
      })
      .catch((error) => {
        console.error("Error uploading file: ", error);
        setError(error);
      });
  }

  return (
    <div className="App">
        <form onSubmit={handleSubmit}>
          <h1>React File Upload</h1>
          <input type="file" onChange={handleChange}/>
          <button type="submit">Upload</button>
        </form>
        {uploadedFile && <img src={uploadedFile} alt="Uploaded content"/>}
        {error && <p>Error uploading file: {error.message}</p>}
    </div>
  );
}

export default App;

 

In the above code, we’ve added a catch block to our axios POST request that sets an error state variable in case of an error. We also render an error message to the screen if there was an error uploading the file.

By extending our application with these two sections, we’ve made it more user-friendly and robust. It’s now not only possible for users to upload files, but also to view the uploaded files and receive error messages in case something goes wrong during the upload process.

React File Upload with the Filestack SDK

The fastest production-ready path, no backend endpoint required. Install the official React SDK:

npm install filestack-react

Then render the picker with your API key (free to obtain; sign up for a Filestack account):

import { useState } from 'react';
import { PickerOverlay } from 'filestack-react';

function App() {
  const [showPicker, setShowPicker] = useState(false);
  const [fileUrl, setFileUrl] = useState(null);

  return (
    <div className="App">
      <h1>React File Upload with Filestack</h1>
      <button onClick={() => setShowPicker(true)}>Upload a file</button>
      {showPicker && (
        <PickerOverlay
          apikey={process.env.REACT_APP_FILESTACK_KEY}
          pickerOptions={{
            accept: ['image/*', '.pdf'],
            maxSize: 100 * 1024 * 1024,
            maxFiles: 5,
          }}
          onUploadDone={(res) => {
            setFileUrl(res.filesUploaded[0]?.url);
            setShowPicker(false);
          }}
        />
      )}
      {fileUrl && <img src={fileUrl} alt="Uploaded content" />}
    </div>
  );
}

export default App;

What you get in those lines: a polished picker UI with drag-and-drop, uploads from 20+ sources (local files, camera, Google Drive, Dropbox, Instagram), chunked multipart uploads with automatic retries for reliability on slow or mobile networks, built-in type/size validation, and a CDN URL for each file that you can transform on the fly (resize, crop, upscale) by adding URL parameters. Keep your API key in an environment variable, and add domain allowlisting in the Filestack dashboard for production.

We’re done! If you want to learn about how to setup the backend server that would receive the POST request we made in this article, check out our articles on how to upload a file using NodeJS (or with Python if you prefer).

If you don’t want to go through the trouble of setting up a server, consider signing up for a free Filestack account.

Popular Questions and Prompts About React File Uploads

These are the highest-demand questions and prompt patterns people type into Google and AI assistants about React file uploads right now.

“How do I upload a file in React?”

Use <input type="file"> with an onChange handler to store the file in state, append it to a FormData object, and POST it with fetch or Axios using multipart/form-data. Handle the response to show success, the uploaded file, or an error. Complete code is in the tutorial above; the filestack-react SDK does the same with no backend.

“How do I add drag-and-drop file upload in React?”

Either handle the browser’s dragover and drop events on a container div and read event.dataTransfer.files into the same upload flow, or use a component that ships drag-and-drop built in; the Filestack picker includes it by default, as does the react-dropzone library for custom UIs.

“How do I upload large files in React without failures?”

Split large files into chunks (5-10 MB), upload chunks with automatic retries, and resume after network interruptions instead of restarting. Filestack’s SDK performs chunked multipart uploads with retry by default; building it manually means slicing with Blob.slice and reassembling server-side.

“Does this work with Next.js?”

Yes. The from-scratch flow works in any React setup; in the Next.js App Router, mark the component with 'use client' since file inputs and upload state are client-side. The filestack-react picker is also a client component; your API route or server action can handle any post-upload processing.

Prompt: “Build a React file uploader”

A pattern that works well with AI coding assistants: “Build a React (Vite) component using filestack-react‘s PickerOverlay (API key from an env variable) that accepts images and PDFs up to 100 MB, allows up to 5 files with drag-and-drop, shows each uploaded file’s CDN URL and a thumbnail, and handles onUploadDone and error states.” Naming the exact SDK, limits, and states is the biggest quality lever.

FAQs

What is the purpose of the ‘handleChange’ function in the file upload process?

The ‘handleChange’ function is used to update the state with the file that a user chooses to upload. It sets the ‘file’ state variable to the file object from the event triggered by the file input.

Why do we use ‘multipart/form-data’ as the content-type in the config object?

When you want to upload a file, the ‘content-type’ should be set to ‘multipart/form-data’. This type is necessary when you are sending binary data in the body of the request, like the contents of a file.

How does the React app display the uploaded file?

Once the file is uploaded, the server returns the URL of the uploaded file which is then stored in the ‘uploadedFile’ state variable. This URL is used as the ‘src’ attribute of an img tag, allowing the uploaded image to be displayed in the React app.

How does the app handle errors that might occur during the file upload process?

The app handles errors by using a .catch() block with the axios POST request. If an error occurs during the file upload process, the catch block is executed, setting the ‘error’ state variable with the error message. This error message is then displayed on the screen.

What if I don’t want to set up a server for file upload functionality?

If you don’t want to set up a server, you could use services like Filestack. We provide SDKs & APIs for file uploading functionality that you can integrate into your app with just a few lines of code.

What is the best file upload library for React?

For a complete managed solution, picker UI, chunked uploads, storage, CDN, filestack-react is the fastest to production. For custom UIs where you handle storage yourself, react-dropzone (drag-and-drop) plus Axios is the common pairing. Plain input + FormData needs no library at all.

How do I limit file size and type in a React upload?

Check file.size and file.type in your onChange handler before uploading and show an error if they fail (see the validation section above). With Filestack, pass accept and maxSize in pickerOptions. Always enforce the same limits server-side.

How do I show upload progress in React?

With Axios, pass an onUploadProgress callback in the request config, compute the percentage from progressEvent.loaded / progressEvent.total, store it in state, and render a <progress> element. The Filestack picker displays per-file progress automatically.

Do I need a backend server to upload files from React?

Only if you build uploads from scratch; the browser must send files somewhere. With a service like Filestack, the SDK uploads directly from the browser to Filestack’s infrastructure and returns a CDN URL, so no upload endpoint of your own is required.

 

Read More →

Ready to get started?

Create an account now!