Building a Custom Image Upload and Preview Component in React with TypeScript

A person is working on a laptop computer

To implement a custom image upload and preview feature in a React component using TypeScript, you can follow these steps:

Step 1: Set Up the Component

First, create a state to handle the selected file and its preview URL.

import React, { useState } from 'react';

const ImageUpload: React.FC = () => {
  const [selectedImage, setSelectedImage] = useState<File | null>(null);
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);

  const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      setSelectedImage(file);
      const objectUrl = URL.createObjectURL(file);
      setPreviewUrl(objectUrl);
    }
  };

  const handleImageRemove = () => {
    setSelectedImage(null);
    setPreviewUrl(null);
  };

  return (
    <div>
      <input
        type="file"
        accept="image/*"
        onChange={handleImageChange}
        style={{ display: 'none' }}
        id="upload-input"
      />
      <label htmlFor="upload-input" className="cursor-pointer">
        <div className="upload-button">Upload Image</div>
      </label>
      {previewUrl && (
        <div className="preview-container">
          <img src={previewUrl} alt="Preview" className="preview-image" />
          <button onClick={handleImageRemove} className="remove-button">
            Remove Image
          </button>
        </div>
      )}
    </div>
  );
};

export default ImageUpload;

Step 2: Styling the Component

You can add styles for the custom upload button and preview section. If you’re using Tailwind CSS, the styles might look something like this:

.upload-button {
  padding: 8px 16px;
  background-color: #4a90e2;
  color: white;
  border-radius: 4px;
  text-align: center;
}

.preview-container {
  margin-top: 16px;
}

.preview-image {
  width: 100%;
  max-width: 300px;
  height: auto;
  border-radius: 8px;
  display: block;
}

.remove-button {
  margin-top: 8px;
  padding: 4px 8px;
  background-color: #e74c3c;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

Step 3: Integrating Icons (Optional)

Since you’re using the Lucide library for icons, you could replace the “Upload Image” text and remove button with icons:

import { Upload, Trash2 } from 'lucide-react';

<label htmlFor="upload-input" className="cursor-pointer">
  <Upload className="icon" />
</label>

<button onClick={handleImageRemove} className="remove-button">
  <Trash2 className="icon" />
</button>

Final Touches

Ensure to clean up the object URL to avoid memory leaks when the component unmounts:

React.useEffect(() => {
  return () => {
    if (previewUrl) {
      URL.revokeObjectURL(previewUrl);
    }
  };
}, [previewUrl]);

This setup will give you a clean and customizable image upload with a preview feature.