Build a Drag & Drop Image File Upload with React and Material UI

Implementing a user-friendly image upload feature can significantly enhance your React application’s functionality and user experience. In this tutorial, we’ll walk through creating a drag-and-drop image file upload component using React and Material UI. You’ll learn how to combine these powerful tools to build an intuitive, visually appealing interface that allows users to easily upload images by simply dragging and dropping files onto the designated area.

Install & Setup Vite + React + Typescript + MUI 5

React MUI 5 Drag and Drop Image File Upload

For a drag and drop file upload component using React and MUI v5, you can create a component that uses the <input type=”file”> element along with drag and drop events. 

Now, let’s create a DragDropFileUpload component.

import { useCallback, useState } from 'react';
import { Box, Paper, Typography, IconButton } from '@mui/material';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';

function DragDropFileUpload({ onFileUpload }) {
  const [dragOver, setDragOver] = useState(false);

  const handleDragOver = useCallback((event) => {
    event.preventDefault();
    setDragOver(true);
  }, []);

  const handleDragLeave = useCallback((event) => {
    event.preventDefault();
    setDragOver(false);
  }, []);

  const handleDrop = useCallback(
    (event) => {
      event.preventDefault();
      setDragOver(false);
      if (event.dataTransfer.files && event.dataTransfer.files[0]) {
        onFileUpload(event.dataTransfer.files[0]);
      }
    },
    [onFileUpload]
  );

  const handleChange = useCallback(
    (event) => {
      if (event.target.files && event.target.files[0]) {
        onFileUpload(event.target.files[0]);
      }
    },
    [onFileUpload]
  );

  return (
    <Paper
      variant="outlined"
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      style={{
        border: dragOver ? '2px dashed #000' : '2px dashed #aaa',
        padding: 20,
        textAlign: 'center',
        cursor: 'pointer',
        background: dragOver ? '#eee' : '#fafafa',
      }}
    >
      <input
        accept="image/*"
        style={{ display: 'none' }}
        id="raised-button-file"
        multiple
        type="file"
        onChange={handleChange}
      />
      <label htmlFor="raised-button-file">
        <Box display="flex" flexDirection="column" alignItems="center">
          <IconButton color="primary" aria-label="upload picture" component="span">
            <CloudUploadIcon style={{ fontSize: 60 }} />
          </IconButton>
          <Typography>Drag and drop files here or click to select files</Typography>
        </Box>
      </label>
    </Paper>
  );
}

export default DragDropFileUpload;

In your parent component, you would use the DragDropFileUpload component like so.

import React from 'react';
import DragDropFileUpload from './DragDropFileUpload';

function App() {
  const handleFileUpload = (file) => {
    console.log(file);
  };

  return (
    <div style={{ padding: 50 }}>
      <DragDropFileUpload onFileUpload={handleFileUpload} />
    </div>
  );
}

export default App;
Drag & Drop Image File

React Material UI Drag and Drop Image Upload with Preview

To add an image preview functionality to the drag and drop file upload component, you would need to update the component to handle the file object, read it as a data URL, and display it as an image preview.

import { useCallback, useState } from 'react';
import { Box, Paper, Typography, IconButton, Grid, CircularProgress } from '@mui/material';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';

function DragDropFileUpload({ onFileUpload }) {
  const [dragOver, setDragOver] = useState(false);
  const [loading, setLoading] = useState(false);
  const [imagePreview, setImagePreview] = useState(null);

  const handleDragOver = useCallback((event) => {
    event.preventDefault();
    setDragOver(true);
  }, []);

  const handleDragLeave = useCallback((event) => {
    event.preventDefault();
    setDragOver(false);
  }, []);

  const handleDrop = useCallback(
    (event) => {
      event.preventDefault();
      setDragOver(false);
      const files = event.dataTransfer.files;
      if (files && files[0]) {
        handleFileChange(files[0]);
      }
    },
    []
  );

  const handleFileChange = (file) => {
    setLoading(true);
    onFileUpload(file); 
    const reader = new FileReader();
    reader.onloadend = () => {
      setLoading(false);
      setImagePreview(reader.result);
    };
    reader.readAsDataURL(file);
  };

  const handleChange = useCallback(
    (event) => {
      const files = event.target.files;
      if (files && files[0]) {
        handleFileChange(files[0]);
      }
    },
    []
  );

  return (
    <Box>
      <Paper
        variant="outlined"
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        onDrop={handleDrop}
        style={{
          border: dragOver ? '2px dashed #000' : '2px dashed #aaa',
          padding: 20,
          textAlign: 'center',
          cursor: 'pointer',
          background: dragOver ? '#eee' : '#fafafa',
          position: 'relative',
        }}
      >
        <input
          accept="image/*"
          style={{ display: 'none' }}
          id="raised-button-file"
          multiple
          type="file"
          onChange={handleChange}
        />
        <label htmlFor="raised-button-file">
          <Box display="flex" flexDirection="column" alignItems="center">
            <IconButton color="primary" aria-label="upload picture" component="span">
              <CloudUploadIcon style={{ fontSize: 60 }} />
            </IconButton>
            <Typography>Drag and drop files here or click to select files</Typography>
          </Box>
        </label>
        {loading && (
          <CircularProgress
            size={24}
            style={{
              position: 'absolute',
              top: '50%',
              left: '50%',
              marginTop: '-12px',
              marginLeft: '-12px',
            }}
          />
        )}
      </Paper>
      {imagePreview && (
        <Grid container justifyContent="center" style={{ marginTop: 16 }}>
          <Grid item xs={12} sm={6} md={4}>
            <Box
              component="img"
              src={imagePreview}
              alt="Image Preview"
              sx={{ width: '100%', height: 'auto' }}
            />
          </Grid>
        </Grid>
      )}
    </Box>
  );
}

export default DragDropFileUpload;

Next, simply import and use the DragDropFileUpload component in your parent component.

import React from 'react';
import DragDropFileUpload from './DragDropFileUpload';

function App() {
  const handleFileUpload = (file) => {
    console.log(file);
  };

  return (
    <div style={{ padding: 50 }}>
      <DragDropFileUpload onFileUpload={handleFileUpload} />
    </div>
  );
}

export default App;
drag and drop image upload to preview in react material ui
Aaronn
Aaronn

Hey there! I'm Aaronn, a Full Stack Developer who's all about React, NEXTJS, Node.js, and Tailwind CSS. I'm on a mission to craft awesome websites that look great and work even better.