React Dropzone Uploader
React Dropzone Uploader is a customizable HTML5 file dropzone and uploader for React, with progress indicators, upload cancellation and restart, and minimal dependencies.
Features
- Fully controllable via optional props, callbacks, and component injection API
- Rich file metadata and file previews, especially for image, video and audio files
- Detailed upload status and progress, upload cancellation and restart
- Trivially set auth headers and additional upload fields for any upload (see S3 example)
- Modular design allows for use as standalone file dropzone, file input, file uploader
- Easily customize styles using CSS or JSX
- Lightweight at ~15kB, including styles
Installation
npm install --save react-dropzone-uploader
Getting Started
RDU's defaults make it very powerful out of the box. The following code gives your users a dropzone / file input that uploads files to https://httpbin.org/post
, with a button to submit the files after they've been uploaded.
The onChangeStatus
prop is included to show how a file's status changes as it's dropped (or picked) and then uploaded. Check out a live demo here.
import Dropzone from 'react-dropzone-uploader'
const MyUploader = () => {
const getUploadParams = ({ meta }) => {
const url = 'https://httpbin.org/post'
const fileUrl = `${url}/${encodeURIComponent(meta.name)}`
return { url, meta: { fileUrl } }
}
const handleChangeStatus = ({ meta, file }, status) => { console.log(status, meta, file) }
const handleSubmit = (files) => { console.log(files.map(f => f.meta)) }
return (
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
/>
)
}
Want to disable the file input? Just pass null
for InputComponent
. Don't need a submit button after files are uploaded? Pass null
for SubmitButtonComponent
, or simply omit the onSubmit
prop.
Don't want to upload files? Omit getUploadParams
, and you'll have a dropzone that just calls onChangeStatus
every time you add a file. This callback receives a fileWithMeta
object and the file's status
. If status is 'done'
, the file has been prepared and validated. Add it to an array of accepted files, or do whatever you want with it. And don't worry, onChangeStatus
won't be called multiple times for the same status.
By the way, getUploadParams
can be async, in case you need to go over the network to get upload params for a file. This would be the case if you use, for example, presigned upload URLs with S3.
To filter which files can be dropped or picked, you can use the accept
prop, which is really the HTML5 input accept attribute. Also available are the minSizeBytes
, maxSizeBytes
and maxFiles
props.
Files whose sizes fall outside the range [minSizeBytes, maxSizeBytes]
are rendered in the dropzone with a special error status. Files rejected because they don't have the correct type, or because they exceed your max number of files, call onChangeStatus
with special status values, but are not rendered.
If you need totally custom filter logic, you can pass a generic validate
function. This function receives a fileWithMeta
object. If you return a falsy value from validate
, the file is accepted, else it's rejected. Read more in the Props section below.
getUploadParams
getUploadParams
is a regular or async callback that takes a fileWithMeta
object and returns the params needed to upload the file. If this prop isn't passed, then RDU will not initiate and manage file uploads.
It should return an object with { fields (object), headers (object), meta (object), method (string), url (string) }
.
The only required key is url
. POST is the default method. fields
lets you append fields to the formData instance submitted with the request. headers
sets headers using XMLHttpRequest.setRequestHeader
, which makes it easy to authenticate with the upload server.
Returning a meta
object lets you merge new values into the file's meta
, which is also something you can do with onChangeStatus
.
fileWithMeta Objects
RDU maintains an array of files it's currently tracking and rendering. The elements of this array are fileWithMeta
objects, which contain the following keys:
file
- file instance returned by
onDrop
event or by input's onChange
event
meta
- file metadata, containing a subset of the following keys:
status
, type
, name
, uploadedDate
, percent
, size
, lastModifiedDate
, previewUrl
, duration
, width
, height
, videoWidth
, videoHeight
; see Props section for possible status values
cancel
, restart
, remove
- functions that allow client code to take control of upload lifecycle; cancel file upload, (re)start file upload, or remove file from dropzone
xhr
- instance of
XMLHttpRequest
if the file is being uploaded, else undefined
RDU's callback props onChangeStatus
, getUploadParams
, and validate
receive a fileWithMeta
object, while onSubmit
receives an array of fileWithMeta
objects.
For convenience, onChangeStatus
also receives the array of fileWithMeta
objects being tracked by the dropzone as a third argument.
These objects give you all the metadata you could want for creating a customized, reactive file dropzone, file input, or file uploader.
Note that fileWithMeta
objects are mutable. If you mutate them, RDU may behave unexpectedly, so don't do this!
This is why, for example, onChangeStatus
receives fileWithMeta and status instead of just fileWithMeta. Client code gets the correct, immutable value of status
when onChangeStatus
was called, even if fileWithMeta
is later mutated.
getUploadParams
and onChangeStatus
have an explicit API for merging new values into a file's meta. If you return something like { meta: { newKey: newValue } }
from these functions, RDU merges the new values into the file's meta
.
Customization
Notice the "Drop Or Pick Files" text that appears by default in an empty dropzone? This is likely something you'll want to change. You can use the inputContent
and inputWithFilesContent
props to render any string or JSX you want. The latter is for the content that's rendered if the dropzone has files. If you'd rather not render file input content, just pass null
.
Want to change submitButtonContent
from its default value of "Submit"? Just pass a new string or JSX for this prop. To kill this text, just pass an empty string or null.
See all of the customization props in the Props section.
Custom Styles
RDU's default styles are defined using CSS. They can be overridden using the classNames
and styles
props, which expose RDU's simple, flexible styling framework.
Both classNames
and styles
should be objects containing a subset of the following keys:
dropzone
dropzoneActive
- wrapper for dropzone on drag over; this is added to the
dropzone
class
input
preview
previewImage
submitButtonContainer
- wrapper for submit button div
submitButton
Each key points to a default CSS class bundled with RDU. A class can be overridden by pointing its key to a different class name, or it can be removed by pointing its key to the empty string ''
.
If you prefer to use style object literals instead of CSS classes, point a key to a style object. The style object is passed to the target component's style
prop, which means it takes precedence over its default class, but doesn't overwrite it.
To overwrite it, you can remove the default class by passing an empty string inside the classNames
prop.
As with any React component, declaring your styles
object inside your render method can hurt performance, because it will cause RDU components that use these styles to re-render even if their props haven't changed.
Adding To Default Classes
If you want to merge your class names with RDU's default classes, use the addClassNames
prop. Added class names work like classNames
, except instead of overriding default classes they are added (concatenated) to them.
You can use both classNames
and addClassNames
if you want to overwrite some classes and add to others.
Component Injection API
If no combination of props controlling styles and content achieves the look and feel you want, RDU provides a component injection API as an escape hatch. The InputComponent
, PreviewComponent
, SubmitButtonComponent
, LayoutComponent
props can each be used to override their corresponding default components. These components receive the props they need to react to the current state of the dropzone and its files (see the Props Passed to Injected Components section below).
null
ing these props removes their corresponding components.
The file input and submit button are simple, and it's usually easy to get the right look and feel with the ...Content and classNames props. For the file preview, these props might not be enough. In this case you can pass a custom PreviewComponent
, which should be a React component. The custom component receives the same props that would have been passed to the default component.
It's worth noting that LayoutComponent
receives an extra
prop, an object containing every callback and piece of state managed by Dropzone
. Overriding this component is the ultimate escape hatch, but also unnecessary except in rare cases.
Props
The following props can be passed to Dropzone
.
Dropzone.propTypes = {
onChangeStatus: PropTypes.func,
getUploadParams: PropTypes.func,
onSubmit: PropTypes.func,
accept: PropTypes.string,
minSizeBytes: PropTypes.number.isRequired,
maxSizeBytes: PropTypes.number.isRequired,
maxFiles: PropTypes.number.isRequired,
validate: PropTypes.func,
autoUpload: PropTypes.bool,
previewTypes: PropTypes.arrayOf(PropTypes.oneOf(['image', 'audio', 'video'])),
InputComponent: PropTypes.func,
PreviewComponent: PropTypes.func,
SubmitButtonComponent: PropTypes.func,
LayoutComponent: PropTypes.func,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
canCancel: PropTypes.bool,
canRestart: PropTypes.bool,
canRemove: PropTypes.bool,
dropzoneContent: PropTypes.node,
dropzoneWithFilesContent: PropTypes.node,
inputContent: PropTypes.node,
inputWithFilesContent: PropTypes.node,
submitButtonContent: PropTypes.node,
submitButtonDisabled: PropTypes.bool,
classNames: PropTypes.object,
styles: PropTypes.object,
}
Props Passed to Injected Components
If you use the component injection API, you'll want to know which props are passed to your injected components. Just scroll to the bottom of the following files to see their prop types.
Example: S3 Uploader
Let's say you want to upload a file to one of your S3 buckets. You have an API service class, myApiService
, that can send requests to your API to get file upload params.
Maybe your API uses Boto to do this. If a request is successful, it returns { fields, uploadUrl, fileUrl }
, else it returns {}
. A successful response looks like this:
{
"fields": {
"AWSAccessKeyId": "AKIAJSQUO7ORWYVCSV6Q",
"acl": "public-read",
"key": "files/89789486-d94a-4251-a42d-18af752ab7d2-test.txt",
"policy": "eyJleHBpcmF0aW9uIjogIjIwMTgtMTAtMzBUMjM6MTk6NDdaIiwgImNvbmRpdGlvbnMiOiBbeyJhY2wiOiAicHVibGljLXJlYWQifSwgWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsIDEwLCAzMTQ1NzI4MF0sIHsiYnVja2V0IjogImJlYW10ZWNoLWZpbGUifSwgeyJrZXkiOiAiY29tcGFueS8zLzg5Nzg5NDg2LWQ5NGEtNDI1MS1hNDJkLTE4YWY3NTJhYjdkMi10ZXN0LnR4dCJ9XX0=",
"signature": "L7r3KBtyOXjUKy31g42JTYb1sio="
},
"fileUrl": "https://my-bucket.s3.amazonaws.com/files/89789486-d94a-4251-a42d-18af752ab7d2-test.txt",
"uploadUrl": "https://my-bucket.s3.amazonaws.com/"
}
Fields has everything you need to authenticate with your S3 bucket, but you need to add them to the request sent by RDU. It turns out this is super easy.
const getUploadParams = async ({ meta: { name } }) => {
const { fields, uploadUrl, fileUrl } = await myApiService.getPresignedUploadParams(name)
return { fields, meta: { fileUrl }, url: uploadUrl }
}
That's it. If myApiService.getPresignedUploadParams
is successful, you return uploadUrl
as url
. Note that you also merge fileUrl
into your file's meta so you can use it later. RDU takes care of the rest, including appending the fields to the formData
instance used in the XMLHttpRequest
.
If myApiService.getPresignedUploadParams
fails, uploadUrl
, and hence url
, are undefined. RDU abandons the upload and changes the file's status to 'error_upload_params'
. At this point you might show the user an error message, and the user might remove the file or restart the upload.
Thanks
Thanks to react-dropzone
, react-fine-uploader
, react-select
, and redux-form
for inspiration.
This library is available as an ES Module at https://unpkg.com/react-dropzone-uploader@VERSION/dist/react-dropzone-uploader.umd.js.