Mastering Typescript React JS Image Upload and Preview

Whether you are building a website, a mobile application, or a desktop app, images play a vital role in enhancing the user experience. As a React developer, mastering image upload and preview using React JS is one of the most common features for your projects.

In this step-by-step guide, we will explore how to implement image uploading and preview functionality in React JS using Typescript. We will cover everything from setting up the project to handling user interactions, including drag and drop, validation, and displaying previews. This example works with Node v18.0.

The other use cases will become apparent as we move forward through this tutorial. Let’s get started .

Setting up the project with Typescript

Before we dive into the implementation details, let’s set up the project with Typescript. The first step is to create a new React JS project using the create-react-app CLI. Open your terminal and run the following command:

npx create-react-app react-img-ts --template typescript
or 
yarn create react-app react-img-ts --template typescript

This command creates a new React JS project called “react-image-ts” with Typescript support. Once the project is created, navigate to the project directory by running the following command:

cd react-img-ts

Next, open the project in your favorite code editor. We will be using Visual Studio Code for this tutorial.

react image upload tsx

Creating the image upload form In React JS

The first step is to create the image upload form. We will be creating a simple form that allows users to select an image file from their device. To create the form, open the “src/App.tsx” file and replace the existing code with the following code:

import React, { useState } from 'react';

function App() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);

  const handleFileInput = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files ? event.target.files[0] : null;
    setSelectedFile(file);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(selectedFile);
  };

  return (
    <div className="App">
      <h1>React JS Image Upload and Preview</h1>
      <form onSubmit={handleSubmit}>
        <input type="file" onChange={handleFileInput} />
        <button type="submit">Upload</button>
      </form>
    </div>
  );
}

export default App;

Let’s go through the code to understand what we are doing.

  • We first import the necessary React hooks and styles. We then define a functional component called “App”.
  • Inside the component, we define a state variable called “selectedFile” using the useState hook. This variable will store the selected image file.
  • We then define two functions, "handleFileInput" and "handleSubmit".
  • The “handleFileInput” function is called when the user selects an image file from their device. It extracts the selected file from the event object and updates the “selectedFile” state variable.
  • The handleSubmit function is called when the user submits the form. It prevents the default form submission behavior and logs the selected file to the console for now.
  • Finally, we render the form with an input element of type “file” and a button element of type submit.

Handling user interactions – drag and drop, file validation

Now that we have created the image upload form, let’s enhance it by adding drag and drop functionality and file validation. Drag and drop allows users to drag and drop an image file onto the form instead of selecting it using the file input element. File validation ensures that the selected file is an image file of a supported format and size. To add these features, update the “src/App.tsx” file with the following code

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

function App() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [dragging, setDragging] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const iconRef = useRef<HTMLInputElement>(null!);
  const onBtnClick = () => {
    /*Collecting node-element and performing click*/
    iconRef?.current.click();
  }
  const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(true);
  };

  const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
  };

  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
  };

  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
    const file = event.dataTransfer.files[0];
    console.log(file);
    validateFile(file);
  };

  const handleFileInput = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files ? event.target.files[0] : null;
    console.log(file);
    validateFile(file);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(selectedFile);
  };

  const validateFile = (file: File | null) => {
    if (file) {
      if (!file.type.startsWith('image/')) {
        setError('Please select an image file');
      } else if (file.size > 1000000) {
        setError('File size is too large');
      } else {
        setSelectedFile(file);
        setError('');
      }
    }
  };

  return (
    <div>
      {' '}
      <h1 style={{ textAlign: 'center' }}>React JS Image Upload and Preview </h1>{' '}

      <div className="container">
        <div className="row ">
          <div className="col-md-4 ">

          </div>
          <div className="col-md-4">
            <div className="upload_zone"
              onDragEnter={handleDragEnter}
              onDragLeave={handleDragLeave}
              onDragOver={handleDragOver}
              onDrop={handleDrop}
            >
              <header >File Uploader JavaScript</header>
              <form onSubmit={handleSubmit}>
                <input ref={iconRef} className="file-input" type="file" onChange={handleFileInput} name="file" hidden />
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-upload" viewBox="0 0 16 16" onClick={onBtnClick}>
                  <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
                  <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
                </svg>
                <p>Or</p>
                <p>Drag and Drop to Upload</p>
              </form>

              <section className="progress-area"></section>
              <section className="uploaded-area"></section>
            </div>
            <div className="col-md-4 ">

            </div>

          </div>
        </div>
      </div>

    </div>
  );
}

export default App;

Let’s go through the changes we made.

  • We first define two new state variables, “dragging” and “error“, using the useState hook. “dragging” represents whether the user is currently dragging a file over the form, and “error” represents any validation error that occurs during file selection.
  • We then define four new functions, “handleDragEnter“, “handleDragLeave”, “handleDragOver“, and “handleDrop“. These functions handle the drag and drop events.
  • The “handleDragEnter” function is called when the user drags a file over the form. It prevents the default drag and drop behavior and sets the “dragging” state variable to true.
  • The “handleDragLeave” function is called when the user drags a file out of the form. It prevents the default drag and drop behavior and sets the “dragging” state variable to false.
  • The “handleDragOver” function is called when the user drags a file over the form without dropping it. It prevents the default drag and drop behavior.
  • The “handleDrop” function is called when the user drops a file onto the form. It prevents the default drag and drop behavior, sets the “dragging” state variable to false, and validates the dropped file.
  • We also update the “handleFileInput” function to validate the selected file before updating the “selectedFile” state variable.

We then update the form to add a new “drop-zone” div element that represents the drop zone for drag and drop. We also add a new paragraph element that separates the file input element and the drop zone. We conditionally render an error message if any validation error occurs.

Finally, we update the “className” attribute of the “div” element to add the “dragging” class when the “dragging” state variable is true. This allows us to style the drop zone differently when the user is dragging a file over it.

Uploading images to the server

Now that we have implemented image selection and validation, it’s time to upload the selected image to the server. To upload the image, we will be using the FormData API and the fetch API. Update the “src/App.tsx” file with the following code:

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

function App() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [dragging, setDragging] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const iconRef = useRef<HTMLInputElement>(null!);


  const onBtnClick = () => {
    /*Collecting node-element and performing click*/
    iconRef?.current.click();
  }
  const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(true);
  };
 
  const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
  };

  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
  };

  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
    const file = event.dataTransfer.files[0];
   
    console.log(file);
    validateFile(file);
  };

  const handleFileInput = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files ? event.target.files[0] : null;
    validateFile(file);
  };

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); if (selectedFile) {
        const formData = new FormData();
        formData.append('image', selectedFile);
        try {
            const response = await fetch('https://<server_ip>/api/v1/upload', { method: 'POST', body: formData, });
            if (response.ok) {
                console.log('Upload successful');
            }
        } catch (error) {
            console.error(error);
        }
    }

};
  const validateFile = (file: File | null) => {
    if (file) {
      if (!file.type.startsWith('image/')) {
        setError('Please select an image file');
      } else if (file.size > 1000000) {
        setError('File size is too large');
      } else {
        setSelectedFile(file);
        setError('');
      }
    }
  };

  return (
    <div>
      {' '}
      <h1 style={{ textAlign: 'center' }}>React JS Image Upload and Preview </h1>{' '}

      <div className="container">
        <div className="row ">
          <div className="col-md-4 ">

          </div>
          <div className="col-md-4">
            <div className="upload_zone"
              onDragEnter={handleDragEnter}
              onDragLeave={handleDragLeave}
              onDragOver={handleDragOver}
              onDrop={handleDrop}
            >
              <header >File Uploader JavaScript</header>
              <form onSubmit={handleSubmit}>
                <input ref={iconRef} className="file-input" type="file" onChange={handleFileInput} name="file" hidden />
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-upload" viewBox="0 0 16 16" onClick={onBtnClick}>
                  <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
                  <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
                </svg>
                <p>Or</p>
                <p>Drag and Drop to Upload</p>
                
              </form>
              {error}
              <button type="button"  onClick={()=>handleSubmit} className="btn btn-primary">Upload</button>

              
            </div>


          </div>
        </div>
      </div>

    </div>
  );
}

export default App;

Let’s go through the changes we made.

We first update the “handleSubmit” function to upload the selected file to the server using the fetch API. We create a new FormData object and append the selected file to it. We then make a POST request to the "/api/v1/upload" endpoint with the FormData object as the request body. We use the “await” keyword to wait for the response from the server. If the response is successful, we log a message to the console. If an error occurs, we log the error to the console.

We also update the "handleSubmit" function to check if a file has been selected before uploading it to the server . Replace the server_ip with your server IP address where image api is hosted .

We will get something below

Displaying image previews before upload

Uploading images to the server is great, but it’s even better if we can display a preview of the selected image before uploading it. To display a preview, we will be using the URL.createObjectURL API. Update the "src/App.tsx" file with the following code:

import React, { useState, useRef } from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [dragging, setDragging] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const iconRef = useRef<HTMLInputElement>(null!);
  const [preview, setPreview] = useState('')
  const onBtnClick = () => {
    /*Collecting node-element and performing click*/
    iconRef?.current.click();
  }
  const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(true);
  };
 
  const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
  };
  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
  };
  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setDragging(false);
    const file = event.dataTransfer.files[0];
    console.log(URL.createObjectURL(file))
    setPreview(URL.createObjectURL(file))
    console.log(file);
    validateFile(file);
  };
  const handleFileInput = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files ? event.target.files[0] : null;
    validateFile(file);
  };
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(selectedFile);
  };
  const validateFile = (file: File | null) => {
    if (file) {
      if (!file.type.startsWith('image/')) {
        setError('Please select an image file');
      } else if (file.size > 1000000) {
        setError('File size is too large');
      } else {
        setSelectedFile(file);
        setError('');
      }
    }
  };
  return (
    <div>
      {' '}
      <h1 style={{ textAlign: 'center' }}>React JS Image Upload and Preview </h1>{' '}
      <div className="container">
        <div className="row ">
          <div className="col-md-4 ">
          </div>
          <div className="col-md-4">
            <div className="upload_zone"
              onDragEnter={handleDragEnter}
              onDragLeave={handleDragLeave}
              onDragOver={handleDragOver}
              onDrop={handleDrop}
            >
              <header >File Uploader JavaScript</header>
              <form onSubmit={handleSubmit}>
                <input ref={iconRef} className="file-input" type="file" onChange={handleFileInput} name="file" hidden />
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-upload" viewBox="0 0 16 16" onClick={onBtnClick}>
                  <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
                  <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
                </svg>
                <p>Or</p>
                <p>Drag and Drop to Upload</p>
              </form>
              {error}
              <section>
                <div className="col-md-4 ">
                  {preview ? <img src={preview} width={244} height={344} /> : ''}
                </div></section>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
export default App;

Output:-

If you see below we are able to preview the image once its Uploaded , we have written a handleSubmit method which uploads image to server.

URL.createObjectURL , will output following in console .

Conclusion :-

In this post we have seen how we can use ReactJS to upload image to server using api call and drag and drop functionality .

Please let us know in comments if you face any difficulty .