Skip to main content

React Dropzone

react-dropzone is a third-party library. Please refer to its official documentation for more details.

warning

It is essential to ensure that when incorporating the react-dropzone component into your project, it is correctly wrapped using the AppReactDropzone component. In scenarios where only a single instance of react-dropzone is utilized, it should be individually enclosed within the AppReactDropzone component. Conversely, if multiple instances of react-dropzone are used in one page, it becomes necessary to wrap the entire page with the AppReactDropzone component to ensure optimal styles.

Upload Multiple Files

// React Imports
import { useState } from 'react'

// MUI Imports
import List from '@mui/material/List'
import Button from '@mui/material/Button'
import ListItem from '@mui/material/ListItem'
import { styled } from '@mui/material/styles'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import type { TypographyProps } from '@mui/material/Typography'
import { useTheme } from '@mui/material/styles'

// Third-party Imports
import { useDropzone } from 'react-dropzone'

type FileProp = {
name: string
type: string
size: number
}

// Styled component for the upload image inside the dropzone area
const Img = styled('img')(({ theme }) => ({
[theme.breakpoints.up('md')]: {
marginRight: theme.spacing(15.75)
},
[theme.breakpoints.down('md')]: {
marginBottom: theme.spacing(4)
},
[theme.breakpoints.down('sm')]: {
width: 160
}
}))

// Styled component for the heading inside the dropzone area
const HeadingTypography = styled(Typography)<TypographyProps>(({ theme }) => ({
marginBottom: theme.spacing(5),
[theme.breakpoints.down('sm')]: {
marginBottom: theme.spacing(4)
}
}))

const FileUploaderMultiple = () => {
// States
const [files, setFiles] = useState<File[]>([])

// Hooks
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles: File[]) => {
setFiles(acceptedFiles.map((file: File) => Object.assign(file)))
}
})

const renderFilePreview = (file: FileProp) => {
if (file.type.startsWith('image')) {
return <img width={38} height={38} alt={file.name} src={URL.createObjectURL(file as any)} />
} else {
return <i className='ri-file-text-line' />
}
}

const handleRemoveFile = (file: FileProp) => {
const uploadedFiles = files
const filtered = uploadedFiles.filter((i: FileProp) => i.name !== file.name)

setFiles([...filtered])
}

const fileList = files.map((file: FileProp) => (
<ListItem key={file.name}>
<div className='file-details'>
<div className='file-preview'>{renderFilePreview(file)}</div>
<div>
<Typography className='file-name'>{file.name}</Typography>
<Typography className='file-size' variant='body2'>
{Math.round(file.size / 100) / 10 > 1000
? `${(Math.round(file.size / 100) / 10000).toFixed(1)} mb`
: `${(Math.round(file.size / 100) / 10).toFixed(1)} kb`}
</Typography>
</div>
</div>
<IconButton onClick={() => handleRemoveFile(file)}>
<i className='ri-close-line text-xl' />
</IconButton>
</ListItem>
))

const handleRemoveAllFiles = () => {
setFiles([])
}

// Hooks
const theme = useTheme()

return (
<>
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<div className='flex gap-3 items-center flex-col md:flex-row'>
<Img alt='Upload img' src='/images/misc/file-upload.png' className='max-bs-[160px] max-is-full bs-full' />
<div className='flex flex-col md:[text-align:unset] text-center'>
<HeadingTypography variant='h5'>Drop files here or click to upload.</HeadingTypography>
<Typography>
Drop files here or click{' '}
<a href='/' onClick={e => e.preventDefault()} className='text-textPrimary no-underline'>
browse
</a>{' '}
thorough your machine
</Typography>
</div>
</div>
</div>
{files.length ? (
<>
<List>{fileList}</List>
<div className='buttons'>
<Button color='error' variant='outlined' onClick={handleRemoveAllFiles}>
Remove All
</Button>
<Button variant='contained'>Upload Files</Button>
</div>
</>
) : null}
</>
)
}

export default FileUploaderMultiple
Upload Single Files

// React Imports
import { useState } from 'react'

// MUI Imports
import Box from '@mui/material/Box'
import { styled } from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import type { TypographyProps } from '@mui/material/Typography'
import { useTheme } from '@mui/material/styles'

// Third-party Imports
import { useDropzone } from 'react-dropzone'

type FileProp = {
name: string
type: string
size: number
}

// Styled component for the upload image inside the dropzone area
const Img = styled('img')(({ theme }) => ({
[theme.breakpoints.up('md')]: {
marginRight: theme.spacing(15.75)
},
[theme.breakpoints.down('md')]: {
marginBottom: theme.spacing(4)
},
[theme.breakpoints.down('sm')]: {
width: 160
}
}))

// Styled component for the heading inside the dropzone area
const HeadingTypography = styled(Typography)<TypographyProps>(({ theme }) => ({
marginBottom: theme.spacing(5),
[theme.breakpoints.down('sm')]: {
marginBottom: theme.spacing(4)
}
}))

const FileUploaderSingle = () => {
// States
const [files, setFiles] = useState<File[]>([])

// Hooks
const { getRootProps, getInputProps } = useDropzone({
multiple: false,
accept: {
'image/*': ['.png', '.jpg', '.jpeg', '.gif']
},
onDrop: (acceptedFiles: File[]) => {
setFiles(acceptedFiles.map((file: File) => Object.assign(file)))
}
})
const theme = useTheme()

const img = files.map((file: FileProp) => (
<img key={file.name} alt={file.name} className='single-file-image' src={URL.createObjectURL(file as any)} />
))

return (
<Box {...getRootProps({ className: 'dropzone' })} {...(files.length && { sx: { height: 450 } })}>
<input {...getInputProps()} />
{files.length ? (
img
) : (
<div className='flex items-center flex-col md:flex-row'>
<Img alt='Upload img' src='/images/misc/file-upload.png' className='max-bs-[160px] max-is-full bs-full' />
<div className='flex flex-col md:[text-align:unset] text-center'>
<HeadingTypography variant='h5'>Drop files here or click to upload.</HeadingTypography>
<Typography>
Drop files here or click{' '}
<a href='/' onClick={e => e.preventDefault()} className='text-textPrimary no-underline'>
browse
</a>{' '}
thorough your machine
</Typography>
</div>
</div>
)}
</Box>
)
}

export default FileUploaderSingle
Upload Files with Restrictions

// React Imports
import { useState } from 'react'

// MUI Imports
import List from '@mui/material/List'
import Button from '@mui/material/Button'
import ListItem from '@mui/material/ListItem'
import { styled } from '@mui/material/styles'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import type { TypographyProps } from '@mui/material/Typography'
import { useTheme } from '@mui/material/styles'

// Third-party Imports
import { toast } from 'react-toastify'

// Icon Imports
import { useDropzone } from 'react-dropzone'

type FileProp = {
name: string
type: string
size: number
}

// Styled component for the upload image inside the dropzone area
const Img = styled('img')(({ theme }) => ({
[theme.breakpoints.up('md')]: {
marginRight: theme.spacing(15.75)
},
[theme.breakpoints.down('md')]: {
marginBottom: theme.spacing(4)
},
[theme.breakpoints.down('sm')]: {
width: 160
}
}))

// Styled component for the heading inside the dropzone area
const HeadingTypography = styled(Typography)<TypographyProps>(({ theme }) => ({
marginBottom: theme.spacing(5),
[theme.breakpoints.down('sm')]: {
marginBottom: theme.spacing(4)
}
}))

const FileUploaderRestrictions = () => {
// States
const [files, setFiles] = useState<File[]>([])

// Hooks
const { getRootProps, getInputProps } = useDropzone({
maxFiles: 2,
maxSize: 2000000,
accept: {
'image/*': ['.png', '.jpg', '.jpeg', '.gif']
},
onDrop: (acceptedFiles: File[]) => {
setFiles(acceptedFiles.map((file: File) => Object.assign(file)))
},
onDropRejected: () => {
toast.error('You can only upload 2 files & maximum size of 2 MB.', {
autoClose: 3000
})
}
})
const theme = useTheme()

const renderFilePreview = (file: FileProp) => {
if (file.type.startsWith('image')) {
return <img width={38} height={38} alt={file.name} src={URL.createObjectURL(file as any)} />
} else {
return <i className='ri-file-text-line' />
}
}

const handleRemoveFile = (file: FileProp) => {
const uploadedFiles = files
const filtered = uploadedFiles.filter((i: FileProp) => i.name !== file.name)

setFiles([...filtered])
}

const fileList = files.map((file: FileProp) => (
<ListItem key={file.name}>
<div className='file-details'>
<div className='file-preview'>{renderFilePreview(file)}</div>
<div>
<Typography className='file-name'>{file.name}</Typography>
<Typography className='file-size' variant='body2'>
{Math.round(file.size / 100) / 10 > 1000
? `${(Math.round(file.size / 100) / 10000).toFixed(1)} mb`
: `${(Math.round(file.size / 100) / 10).toFixed(1)} kb`}
</Typography>
</div>
</div>
<IconButton onClick={() => handleRemoveFile(file)}>
<i className='ri-close-line text-xl' />
</IconButton>
</ListItem>
))

const handleRemoveAllFiles = () => {
setFiles([])
}

return (
<>
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<div className='flex items-center flex-col md:flex-row'>
<Img alt='Upload img' src='/images/misc/file-upload.png' className='max-bs-[160px] max-is-full bs-full' />
<div className='flex flex-col md:[text-align:unset] text-center'>
<HeadingTypography variant='h5'>Drop files here or click to upload.</HeadingTypography>
<Typography>Allowed *.jpeg, *.jpg, *.png, *.gif</Typography>
<Typography>Max 2 files and max size of 2 MB</Typography>
</div>
</div>
</div>
{files.length ? (
<>
<List>{fileList}</List>
<div className='buttons'>
<Button color='error' variant='outlined' onClick={handleRemoveAllFiles}>
Remove All
</Button>
<Button variant='contained'>Upload Files</Button>
</div>
</>
) : null}
</>
)
}

export default FileUploaderRestrictions