react-dnd、react-dropzone、react-dropzone-component、react-file-drop はいずれも React アプリケーションでドラッグ&ドロップ機能を実現するための npm パッケージですが、それぞれ設計目的と適用範囲が大きく異なります。react-dnd は UI 要素間の汎用的なドラッグ&ドロップ操作をサポートするフレームワークであり、ファイル操作には特化していません。一方、react-dropzone はファイルのドロップや選択に特化したモダンで軽量なフックベースのライブラリで、現在最も広く採用されています。react-dropzone-component は非推奨となっており、新規プロジェクトでの使用は推奨されません。react-file-drop もメンテナンスが滞っており、最新の React 環境との互換性に懸念があります。
React アプリケーションでファイルのドラッグ&ドロップや一般的なドラッグ操作を実装する際、いくつかのライブラリが候補になります。この記事では react-dnd、react-dropzone、react-dropzone-component、react-file-drop の4つを、実際の開発現場での使用観点から深く比較します。
react-dnd は汎用的なドラッグ&ドロップ(DnD)フレームワークです。ファイルだけでなく、UI要素同士のドラッグ操作(例:タスクボードのカード移動)にも対応しています。内部で HTML5 Drag and Drop API を抽象化し、柔軟なカスタマイズを可能にします。
react-dropzone はファイルのドロップや選択に特化した軽量なフックベースのライブラリです。HTML5 File API との連携をシンプルにラップし、ファイルの受け取り・検証に焦点を当てています。
react-dropzone-component はかつて存在していたコンポーネントベースのラッパーでしたが、公式 npm ページおよび GitHub リポジトリによると 非推奨(deprecated) となっています。新しいプロジェクトでの使用は避けるべきです。
react-file-drop もファイルドロップ専用のコンポーネントですが、メンテナンス状況が不安定で、最新の React バージョン(特に hooks や strict mode)との互換性に懸念があります。GitHub リポジトリは長期間更新されておらず、公式ドキュメントも限られています。
react-dropzone(推奨される現代的アプローチ)import { useDropzone } from 'react-dropzone';
function MyDropzone() {
const onDrop = (acceptedFiles) => {
console.log(acceptedFiles);
};
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{isDragActive ? (
<p>ここにファイルをドロップしてください...</p>
) : (
<p>ファイルをドラッグ&ドロップ、またはクリックして選択</p>
)}
</div>
);
}
react-file-drop(古いコンポーネントスタイル)import ReactFileDrop from 'react-file-drop';
function MyFileDrop() {
const handleDrop = (files, event) => {
console.log(files);
};
return (
<ReactFileDrop onDrop={handleDrop}>
<div>ファイルをここにドロップ</div>
</ReactFileDrop>
);
}
react-dnd(過剰だが汎用性あり)ファイルドロップを react-dnd で実装するのは非効率ですが、以下のように可能です。
import { useDrop } from 'react-dnd';
import { ItemTypes } from './constants'; // 通常は独自の型を使う
// 注意: react-dnd はネイティブファイルドロップを直接サポートしていないため、
// 実際には HTML5 の onDrop イベントを併用する必要がある
function FileDropTarget() {
const [{ isOver }, drop] = useDrop(() => ({
accept: 'file', // カスタム型として扱う必要あり
drop: (item, monitor) => {
// 実際にはファイル情報は monitor ではなく DOM イベントから取得
},
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
}));
return (
<div ref={drop}>
{isOver ? 'ドロップ可' : 'ここにドロップ'}
</div>
);
}
💡 注:
react-dndは主にアプリ内要素のドラッグ操作向けであり、ファイルドロップには不向きです。公式ドキュメントでもファイル操作への言及はありません。
react-dropzone-component(非推奨)// 非推奨のため、新規コードでの使用は避けてください
import DropzoneComponent from 'react-dropzone-component';
function OldDropzone() {
const config = {
postUrl: '/upload'
};
return <DropzoneComponent config={config} />;
}
react-dropzone: accept、maxSize、minSize、multiple などのオプションをサポート。カスタムバリデーション関数も指定可能。const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
maxSize: 5 * 1024 * 1024, // 5MB
onDropRejected: (rejectedFiles) => {
console.log('無効なファイル:', rejectedFiles);
}
});
react-file-drop: ファイル検証機能は提供されていません。自分で onDrop 内で処理する必要があります。
react-dnd: ファイル検証は想定外の用途のため、すべて手動実装が必要です。
react-dropzone-component: 非推奨のため、機能比較の対象外です。
react-dropzone: isDragActive、isDragAccept、isDragReject といった状態をフック経由で提供。
react-file-drop: onFrameDragEnter / onFrameDragLeave などでグローバルなドラッグ状態を検出可能だが、細かい制御は難しい。
react-dnd: useDrag / useDrop の collect 関数でカスタム状態を収集可能だが、ファイルドロップには不向き。
react-dropzone-component: npm ページに明確な非推奨メッセージがあり、「代わりに react-dropzone を使用せよ」と記載されています。新規プロジェクトでの使用は禁止すべきです。
react-file-drop: 最終更新が2019年以前であり、React 18 の concurrent features や strict mode との互換性が確認されていません。GitHub に未解決の issue も多数残っています。
react-dnd: 現在もアクティブにメンテナンスされており、React 18 に対応していますが、ファイルドロップ用途には設計されていません。
react-dropzone: 現在も積極的に更新されており、TypeScript サポート、React hooks との統合、アクセシビリティ対応など、現代的な要件を満たしています。
react-dropzone が最適です。軽量で、必要な機能だけを提供し、スタイリングも自由です。react-dnd が適しています。ファイルとは無関係な DnD 操作に特化しています。react-dropzone でファイル受信、react-dnd で UI 要素の操作を行うのがベストプラクティスです。| ライブラリ名 | 推奨用途 | 新規プロジェクトで使用可? |
|---|---|---|
react-dropzone | ファイルのドロップ・選択 | ✅ はい(推奨) |
react-dnd | UI要素間のドラッグ&ドロップ | ✅ はい(ただしファイル用途には不向き) |
react-dropzone-component | — | ❌ いいえ(非推奨) |
react-file-drop | — | ⚠️ 非推奨に近い(避けるべき) |
ファイルアップロードが必要なら、迷わず react-dropzone を使ってください。 これは現在の業界標準であり、テストされ、メンテナンスされ、拡張可能な唯一の選択肢です。
UI要素のドラッグ操作が必要なら react-dnd を検討してください。 ただし、ファイルドロップと混同しないように注意。
react-dropzone-component と react-file-drop は新しいコードで使うべきではありません。 既存プロジェクトで使われている場合は、react-dropzone への移行を計画しましょう。
これらの判断は、単なる流行ではなく、長期的な保守性、セキュリティ、パフォーマンス、そして開発体験に基づいています。
react-dropzone は、ファイルのドラッグ&ドロップや選択機能を実装したい場合に最適です。フックベースで軽量、TypeScript 対応、ファイル検証機能、アクセシビリティ対応など、現代的な要件を満たしており、新規プロジェクトでの標準選択肢です。シンプルなファイルアップローダーや画像選択 UI にぴったりです。
react-dnd は、ファイルではなく UI 要素(例:カード、リストアイテム、ウィジェット)のドラッグ&ドロップ操作が必要な場合に選んでください。複雑な DnD ロジック(プレビュー、キャンセル、カスタムタイプ)を実装したいときにも適しています。ただし、ファイルのドロップには向いていないため、ファイル操作と併用する場合は別途 react-dropzone を組み合わせるのが一般的です。
react-file-drop は最終更新が古く、React 18 以降の環境との互換性が保証されていません。ファイルドロップ機能は提供しますが、検証機能やエラーハンドリングが貧弱で、長期的な保守性に問題があります。新規プロジェクトでは避けて、代わりに react-dropzone を使用してください。
react-dropzone-component は公式に非推奨(deprecated)とされており、新しいプロジェクトで使用すべきではありません。既存コードで使われている場合は、react-dropzone への移行を強く推奨します。このパッケージはメンテナンスされておらず、セキュリティや互換性のリスクがあります。

Simple React hook to create a HTML5-compliant drag'n'drop zone for files.
Documentation and examples at https://react-dropzone.js.org. Source code at https://github.com/react-dropzone/react-dropzone/.
Install it from npm and include it in your React build process (using Webpack, Browserify, etc).
npm install --save react-dropzone
or:
yarn add react-dropzone
You can either use the hook:
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
)
}
Or the wrapper component for the hook:
import React from 'react'
import Dropzone from 'react-dropzone'
<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
)}
</Dropzone>
If you want to access file contents you have to use the FileReader API:
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback((acceptedFiles) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader()
reader.onabort = () => console.log('file reading was aborted')
reader.onerror = () => console.log('file reading has failed')
reader.onload = () => {
// Do whatever you want with the file contents
const binaryStr = reader.result
console.log(binaryStr)
}
reader.readAsArrayBuffer(file)
})
}, [])
const {getRootProps, getInputProps} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
The dropzone property getters are just two functions that return objects with properties which you need to use to create the drag 'n' drop zone.
The root properties can be applied to whatever element you want, whereas the input properties must be applied to an <input>:
import React from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps} = useDropzone()
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
Note that whatever other props you want to add to the element where the props from getRootProps() are set, you should always pass them through that function rather than applying them on the element itself.
This is in order to avoid your props being overridden (or overriding the props returned by getRootProps()):
<div
{...getRootProps({
onClick: event => console.log(event),
role: 'button',
'aria-label': 'drag and drop area',
...
})}
/>
In the example above, the provided {onClick} handler will be invoked before the internal one, therefore, internal callbacks can be prevented by simply using stopPropagation.
See Events for more examples.
Important: if you omit rendering an <input> and/or binding the props from getInputProps(), opening a file dialog will not be possible.
Both getRootProps and getInputProps accept a custom refKey (defaults to ref) as one of the attributes passed down in the parameter.
This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g:
import React from 'react'
import {useDropzone} from 'react-dropzone'
// NOTE: After v4.0.0, styled components exposes a ref using forwardRef,
// therefore, no need for using innerRef as refKey
import styled from 'styled-components'
const StyledDiv = styled.div`
// Some styling here
`
function Example() {
const {getRootProps, getInputProps} = useDropzone()
<StyledDiv {...getRootProps({ refKey: 'innerRef' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</StyledDiv>
}
If you're working with Material UI v4 and would like to apply the root props on some component that does not expose a ref, use RootRef:
import React from 'react'
import {useDropzone} from 'react-dropzone'
import RootRef from '@material-ui/core/RootRef'
function PaperDropzone() {
const {getRootProps, getInputProps} = useDropzone()
const {ref, ...rootProps} = getRootProps()
<RootRef rootRef={ref}>
<Paper {...rootProps}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</Paper>
</RootRef>
}
IMPORTANT: do not set the ref prop on the elements where getRootProps()/getInputProps() props are set, instead, get the refs from the hook itself:
import React from 'react'
import {useDropzone} from 'react-dropzone'
function Refs() {
const {
getRootProps,
getInputProps,
rootRef, // Ref to the `<div>`
inputRef // Ref to the `<input>`
} = useDropzone()
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
}
If you're using the <Dropzone> component, though, you can set the ref prop on the component itself which will expose the {open} prop that can be used to open the file dialog programmatically:
import React, {createRef} from 'react'
import Dropzone from 'react-dropzone'
const dropzoneRef = createRef()
<Dropzone ref={dropzoneRef}>
{({getRootProps, getInputProps}) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)}
</Dropzone>
dropzoneRef.open()
react-dropzone makes some of its drag 'n' drop callbacks asynchronous to enable promise based getFilesFromEvent() functions. In order to test components that use this library, you need to use the react-testing-library:
import React from 'react'
import Dropzone from 'react-dropzone'
import {act, fireEvent, render} from '@testing-library/react'
test('invoke onDragEnter when dragenter event occurs', async () => {
const file = new File([
JSON.stringify({ping: true})
], 'ping.json', { type: 'application/json' })
const data = mockData([file])
const onDragEnter = jest.fn()
const ui = (
<Dropzone onDragEnter={onDragEnter}>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
)
const { container } = render(ui)
await act(
() => fireEvent.dragEnter(
container.querySelector('div'),
data,
)
);
expect(onDragEnter).toHaveBeenCalled()
})
function mockData(files) {
return {
dataTransfer: {
files,
items: files.map(file => ({
kind: 'file',
type: file.type,
getAsFile: () => file
})),
types: ['Files']
}
}
}
NOTE: using Enzyme for testing is not supported at the moment, see #2011.
More examples for this can be found in react-dropzone's own test suites.
React 16.8 or above is required because we use hooks (the lib itself is a hook).
Files returned by the hook or passed as arg to the onDrop cb won't have the properties path or fullPath.
For more inf check this SO question and this issue.
This lib is not a file uploader; as such, it does not process files or provide any way to make HTTP requests to some server; if you're looking for that, checkout filepond or uppy.io.
If you use <label> as the root element, the file dialog will be opened twice; see #1107 why. To avoid this, use noClick:
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps} = useDropzone({noClick: true})
return (
<label {...getRootProps()}>
<input {...getInputProps()} />
</label>
)
}
If you bind a click event on an inner element and use open(), it will trigger a click on the root element too, resulting in the file dialog opening twice. To prevent this, use the noClick on the root:
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps, open} = useDropzone({noClick: true})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button type="button" onClick={open}>
Open
</button>
</div>
)
}
The onFileDialogCancel() cb is unstable in most browsers, meaning, there's a good chance of it being triggered even though you have selected files.
We rely on using a timeout of 300ms after the window is focused (the window onfocus event is triggered when the file select dialog is closed) to check if any files were selected and trigger onFileDialogCancel if none were selected.
As one can imagine, this doesn't really work if there's a lot of files or large files as by the time we trigger the check, the browser is still processing the files and no onchange events are triggered yet on the input. Check #1031 for more info.
Fortunately, there's the File System Access API, which is currently a working draft and some browsers support it (see browser compatibility), that provides a reliable way to prompt the user for file selection and capture cancellation.
Also keep in mind that the FS access API can only be used in secure contexts.
NOTE You can enable using the FS access API with the useFsAccessApi property: useDropzone({useFsAccessApi: true}).
When setting useFsAccessApi to true, you're switching to the File System API (see the file system access RFC).
What this essentially does is that it will use the showOpenFilePicker method to open the file picker window so that the user can select files.
In contrast, the traditional way (when the useFsAccessApi is not set to true or not specified) uses an <input type="file"> (see docs) on which a click event is triggered.
With the use of the file system access API enabled, there's a couple of caveats to keep in mind:
We use browserslist config to state the browser support for this lib, so check it out on browserslist.dev.
React Dropzone integrates perfectly with Pintura Image Editor, creating a modern image editing experience. Pintura supports crop aspect ratios, resizing, rotating, cropping, annotating, filtering, and much more.
Checkout the Pintura integration example.
Support us with a monthly donation and help us continue our activities. [Become a backer]
Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor]
react-dropzone.js.org hosting provided by netlify.
Checkout the organization CONTRIBUTING.md.
MIT