Adding image upload feature to your React Application

The functionality for uploading images includes some elements aimed at improving user experience. This includes user-friendly interfaces for choosing and uploading photos.

April 4, 20245 min read

In today’s digital landscape, the ability to upload images seamlessly is a fundamental feature of numerous web and mobile applications. Take a moment to think about applications that you interact with daily. Image uploads are a major cornerstone of social media applications and e-commerce apps like Amazon to enhance user engagement.

A thoughtful and intuitive UX design with an image upload feature should provide visual feedback and image previews before uploading. This helps the user verify that they are uploading the right image. Adding progress indicators and success messages keeps the user aware and engaged during the image upload process.

How do you upload images using React? Let’s dive deep into how to upload an image, preview it, and send it to a backend server.

Step 1: Introduction

Initialize a React application using the vite tool from the command terminal. We will use the yarn package manager to install and run the scripts. Finally, navigate to the project folder and run yarn to install the dependencies.

yarn create vite@latest image-upload -- --template react-ts

Image

Step 2: Creating a file input

This component renders a simple user interface with a file input field that allows users to select image files. The accept attribute is set to "image/*", which specifies that the file input should only accept files with MIME type starting with "image/", effectively limiting file selection to images.

import React from "react";
import "./App.css";

function App() {
  return (
    <div>
      <input type="file" accept="image/*" />
    </div>
  );
}

export default App;

Try Kodaschool for free

Click below to sign up and get access to free web, android and iOs challenges.

Sign Up

Step 3: Storing File in state

When a user selects a file the handleFileChange function is triggered. The image selected by the user is stored in a state variable. The selected file from the file input is retrieved by event.target.filesFinally, the setSelectedImage function updates the selectedImage state with the selected file.

import { FormEvent, ChangeEventHandler, useState } from "react";
import "./App.css";

function App() {
   const [selectedImage, setSelectedImage] = useState<File>();
  
  const handleFileChange: ChangeEventHandler<HTMLInputElement> = (
    event
  ) => {
   const file = event.target.files as FileList;
    setSelectedImage(file?.[0]);
    };
 
  return (
    <div className="wrapper">
       <form>
        <input type="file" onChange={handleFileChange} accept="image/*" />
      </form>
    </div>
  );
}

export default App;

Step 4: Preview the Image

The function below converts a file to a data URL string, encapsulated within a promise to handle the file and get the image preview.

export const fileToDataString = (file: File) => {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onerror = (error) => reject(error);
    reader.onload = () => resolve(reader.result as string);
  });
};

Once a user selects an image, the preview of the image is rendered.

. selectedImage: Stores the selected image file by the user.

. previewImgUrl: Stores the data URL of the selected image f

The fileToDataString function is called with the selected file to convert it into a data URL string representing the image. Upon successful conversion, the obtained data URL is set as the value of previewImgUrl state variable, enabling the image preview.

import { FormEvent, ChangeEventHandler, useState } from "react";
import "./App.css";
import { fileToDataString } from "./utils";
function App() {
  const [selectedImage, setSelectedImage] = useState<File>();
  const [previewImgUrl, setPreviewimgUrl] = useState("");

  const handleFileChange: ChangeEventHandler<HTMLInputElement> = async (
    event
  ) => {
    const file = event.target.files as FileList;
    setSelectedImage(file?.[0]);
    if (!file) {
      return;
    }
    try {
      const imgUrl = await fileToDataString(file?.[0]);
      setPreviewimgUrl(imgUrl);
    } catch (error) {
      console.log(error);
    }
  };
  
  return (
    <div className="wrapper">
      {previewImgUrl && (
        <div className="image_wrapper">
          <img src={previewImgUrl} />
        </div>
      )}
      <form >
        <input type="file" onChange={handleFileChange} accept="image/*" />
      </form>
    </div>
  );
}

export default App;

Step 5: Sending the Image to a server

Finally, the image uploaded from your local machine to the react app can be sent to a server. So we will now make a POST request to a REST API and pass the selectedImage as the payload. We need to install Axios and use it to make the API request.

a) Set up Axios for React TypeScript Client

After installing Axios we set up an Axios client configured with custom headers for sending multipart/form-data.The Axios instance can be used throughout the application to make HTTP requests with these headers preconfigured.

import axios from "axios";

const headers: HeadersInit = {
  "Content-Type": `multipart/form-data;`,
  Accept: "multipart/form-data",
};

export const axiosInstance = axios.create({
  baseURL: "https://localhost:5000",
  headers,
});

b) Method to handle image upload to a server

The handleImageUpload function is used to handle the API request. Inside the try block, a new FormData object is created to store the image file selected by the user.

  • If a selectedImage is available it is appended to the FormData object with the key "file".
  • An Axios POST request is made to /upload/file endpoint using the axiosInstance.
  • The onUploadProgress option is provided in the request configuration to track the upload progress. Whenever progress is made, the progressEvent is used to calculate the percentage of completion, and the progress state variable is updated accordingly using setProgress.
import { FormEvent,useState } from "react"
import { axiosInstance } from "./config";
const [progress, setProgress] = useState(0)
const handleImageUpload = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      const formData = new FormData();
      if (selectedImage) {
        formData.append("file", selectedImage);
        const response = await axiosInstance.post(`/upload/file`, formData, {
          onUploadProgress: (progressEvent) => {
            const progress = Math.round(
              (100 * progressEvent.loaded) / progressEvent.total
            );
            setProgress(progress);
          },
        });
        setProgress(0);
        console.log(response);
      }
    } catch (error) {
      console.log(error);
    }
  };

c) Pass the function to the form onSubmit handler

When the form is submitted, the handleImageUpload function is called, which is responsible for sending the selected image file to the server.

<form onSubmit={handleImageUpload}>
    <input type="file" onChange={handleFileChange} accept="image/*" />
    <button type="submit">Upload image</button>
</form>

Congratulations!! You have done it.

Here is the complete code for the Image upload.

import { FormEvent, ChangeEventHandler, useState } from "react";
import "./App.css";
import { fileToDataString } from "./utils";
import { axiosInstance } from "./config";

function App() {
  const [selectedImage, setSelectedImage] = useState<File>();
  const [previewImgUrl, setPreviewimgUrl] = useState("");
  const [progress, setProgress] = useState<number>(0);

  const handleFileChange: ChangeEventHandler<HTMLInputElement> = async (
    event
  ) => {
    const file = event.target.files as FileList;
    setSelectedImage(file?.[0]);
    if (!file) {
      return;
    }
    try {
      const imgUrl = await fileToDataString(file?.[0]);
      setPreviewimgUrl(imgUrl);
    } catch (error) {
      console.log(error);
    }
  };
  const handleImageUpload = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      const formData = new FormData();
      if (selectedImage) {
        formData.append("file", selectedImage);
        const response = await axiosInstance.post(`/upload/file`, formData, {
          onUploadProgress: (progressEvent) => {
            const progress = Math.round(
              (100 * progressEvent.loaded) / progressEvent.total
            );
            setProgress(progress);
          },
        });
        setProgress(0);
        console.log(response);
      }
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <div className="wrapper">
      {selectedImage && progress > 0 && (
        <div className="progress my-3">
          <div className="progress-bar progress-bar-info" 
  role="progressbar">
            {progress}%
          </div>
        </div>
      )}
      {previewImgUrl && (
        <div className="image_wrapper">
          <img src={previewImgUrl} alt="image"/>
        </div>
      )}

      <form onSubmit={handleImageUpload}>
        <input type="file" onChange={handleFileChange} accept="image/*" />
        <button  disabled={!selectedImage} type="submit">Upload image</button>
      </form>
    </div>
  );
}

export default App;

Mary Maina

About Mary Maina

I am a frontend devloper