react-dropzone vs react-dropzone-uploader vs react-dropzone-component
React 向けファイルアップロードライブラリの比較と選定
react-dropzonereact-dropzone-uploaderreact-dropzone-component類似パッケージ:

React 向けファイルアップロードライブラリの比較と選定

react-dropzonereact-dropzone-componentreact-dropzone-uploader は、すべて React アプリケーションでドラッグ&ドロップによるファイルアップロード機能を実装するためのライブラリです。react-dropzone は UI を提供しないヘッドレスなフックであり、最大の柔軟性を誇ります。react-dropzone-uploader はアップロードロジックと UI を統合したオールインワンソリューションです。react-dropzone-component は、コアライブラリをラップしてすぐに使える UI コンポーネントを提供することを目的としていますが、メンテナンス状況に注意が必要です。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
react-dropzone7,286,79810,983595 kB711ヶ月前MIT
react-dropzone-uploader24,936453-1546年前MIT
react-dropzone-component17,8211,001-968年前MIT

React 向けファイルアップロードライブラリ:アーキテクチャと実装の深層比較

React エコシステムにおいて、ファイルアップロード機能は頻繁に求められる要件ですが、その実装アプローチはライブラリによって大きく異なります。react-dropzonereact-dropzone-componentreact-dropzone-uploader の 3 つは、一見すると同じ目的を持っていますが、内部のアーキテクチャと開発者が負担する責任範囲が明確に違います。本稿では、これらを技術的な観点から比較し、実プロダクトでの選定基準を明確にします。

🏗️ アーキテクチャ:ヘッドレス vs 統合型

この 3 つのライブラリを分ける最大の要素は、「UI をどこが担当するか」と「アップロードロジックをどこが担当するか」です。

react-dropzone は「ヘッドレス」アプローチを取ります。

  • UI は完全に開発者が作成します。
  • ドラッグ&ドロップの検知とファイル取得のみを担当します。
  • アップロード処理(HTTP リクエスト)は自分で実装する必要があります。
// react-dropzone: UI とロジックを自分で制御
import { useDropzone } from 'react-dropzone';

function MyUpload() {
  const onDrop = (acceptedFiles) => {
    // 自分で FormData を作成し、fetch や axios で送信
    const formData = new FormData();
    acceptedFiles.forEach(file => formData.append('file', file));
    fetch('/api/upload', { method: 'POST', body: formData });
  };

  const { getRootProps, getInputProps } = useDropzone({ onDrop });

  return (
    <div {...getRootProps()} style={{ border: '1px solid #ccc' }}>
      <input {...getInputProps()} />
      <p>ここにファイルをドラッグしてください</p>
    </div>
  );
}

react-dropzone-uploader は「統合型」アプローチを取ります。

  • 基本的な UI(進行状況バーなど)が内置されています。
  • アップロードロジックの設定(URL、メソッド)を props で渡します。
  • 進行状況の管理などをライブラリが内部で行います。
// react-dropzone-uploader: ロジック設定を渡して制御
import Dropzone from 'react-dropzone-uploader';

function MyUpload() {
  const getUploadParams = ({ file }) => {
    // ライブラリがここで設定を使って送信してくれる
    return { url: '/api/upload', method: 'POST' };
  };

  return (
    <Dropzone
      getUploadParams={getUploadParams}
      accept="image/*,application/pdf"
      styles={{ dropzone: { minHeight: 200 } }}
    />
  );
}

react-dropzone-component は「ラッパー型」アプローチを取ります。

  • コアの react-dropzone を内部で使用しているラッパーです。
  • 設定オブジェクト(config)を通じて挙動を定義します。
  • UI コンポーネントが内置されていますが、カスタマイズ性は中間です。
// react-dropzone-component: 設定オブジェクトで制御
import { DropzoneComponent } from 'react-dropzone-component';

function MyUpload() {
  const config = {
    allowedFileTypes: ['image/*', 'application/pdf'],
    maxFiles: 5,
    // アップロードハンドラは別途イベントで受け取る場合が多い
  };

  const eventHandlers = {
    init: () => console.log('initialized'),
    addedfile: (file) => console.log('file added', file),
  };

  return (
    <DropzoneComponent
      config={config}
      eventHandlers={eventHandlers}
      djsConfig={{ addRemoveLinks: true }}
    />
  );
}

📤 アップロード処理とサーバー連携

ファイルを選択した後、実際にサーバーへ送信する処理の記述量が大きく異なります。

react-dropzone では、すべてを手動で記述します。

  • onDrop コールバック内で fetchaxios を呼び出します。
  • 進行状況(プログレスバー)を実装するには、XMLHttpRequest または axios の progress イベントを自分で管理する必要があります。
  • 柔軟性は最高ですが、ボイラープレートが増えます。
// react-dropzone: 送信ロジックも手動
const onDrop = (files) => {
  files.forEach(file => {
    const data = new FormData();
    data.append('file', file);
    
    // 自分で進行状況管理をするならここが複雑になる
    axios.post('/upload', data, {
      onUploadProgress: (progressEvent) => {
        const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
        console.log(`Progress: ${percent}%`);
      }
    });
  });
};

react-dropzone-uploader では、設定を返すだけで送信してくれます。

  • getUploadParams で URL を返すと、ライブラリが内部で送信処理を行います。
  • 進行状況の UI も自動で表示されます。
  • カスタムヘッダーや認証トークンの追加も getUploadParams の戻り値で簡単に設定可能です。
// react-dropzone-uploader: 設定を返すだけで OK
const getUploadParams = ({ meta }) => {
  return {
    url: 'https://api.example.com/upload',
    headers: { Authorization: `Bearer ${token}` },
    method: 'POST'
  };
};

// コンポーネント側で自動送信される
<Dropzone getUploadParams={getUploadParams} />

react-dropzone-component では、イベントハンドラを通じて処理します。

  • 内部で Dropzone.js を使用しているため、イベント駆動で処理をフックします。
  • 実際の送信処理は eventHandlers の中で実装するか、設定に依存します。
  • 既存の Dropzone.js の知識があるチームには馴染みやすいです。
// react-dropzone-component: イベントでフック
const eventHandlers = {
  // ファイルが追加された時の処理
  addedfile: (file) => {
    // ここで手動送信ロジックを書くことも可能
  },
  // 送信完了時の処理
  complete: (file) => {
    console.log('Upload complete', file);
  }
};

<DropzoneComponent eventHandlers={eventHandlers} />

🎨 UI カスタマイズとスタイリング

デザイン要件が厳しい場合、どのライブラリが適しているかは重要な選定基準になります。

react-dropzone は CSS を完全に自由に書けます。

  • 提供されるのは props(getRootProps など)のみです。
  • styled-components や Tailwind CSS との相性が最も良いです。
  • 独自のデザインシステムを構築する場合の標準選択肢となります。
// react-dropzone: 自由にクラス名を付けられる
const { getRootProps, getInputProps, isDragActive } = useDropzone();

return (
  <div 
    {...getRootProps()} 
    className={`p-10 border-2 ${isDragActive ? 'border-blue-500' : 'border-gray-300`}
  >
    <input {...getInputProps()} />
    {isDragActive ? <p>ドロップ...</p> : <p>ドラッグ...</p>}
  </div>
);

react-dropzone-uploader は styles props で調整します。

  • 内置の UI 構造を変えるのは難しいですが、色やサイズは styles props で変更可能です。
  • 構造自体を変更したい場合は、カスタム UI コンポーネントを渡す必要がありますが、制限があります。
// react-dropzone-uploader: styles オブジェクトで調整
<Dropzone
  styles={{
    dropzone: { width: '100%', height: '200px', backgroundColor: '#f0f0f0' },
    inputFile: { display: 'none' }
  }}
/>

react-dropzone-component は設定と CSS クラスで調整します。

  • 内部が Dropzone.js に依存しているため、特定のクラス名(dropzonedz-preview など)をターゲットに CSS を書く必要があります。
  • グローバルな CSS 影響を受けやすく、CSS Modules や Scoped CSS との相性は劣ります。
// react-dropzone-component: 内部クラスをターゲットに CSS
/* global.css */
.dropzone {
  border: 2px dashed #666;
}
.dz-preview {
  background: #fff;
}

// JSX
<DropzoneComponent config={{ ... }} />

⚠️ メンテナンス性とエコシステム

ライブラリを選ぶ際は、将来のメンテナンスコストも考慮する必要があります。

react-dropzone は業界標準です。

  • 更新頻度が高く、React の新しいバージョンへの追従も早いです。
  • TypeScript の型定義も充実しており、大規模プロジェクトで安心です。
  • コミュニティが大きく、問題が起きた時の解決策が見つかりやすいです。

react-dropzone-uploader は機能特化型です。

  • 更新頻度は react-dropzone より低めですが、安定しています。
  • アップロード機能に特化しているため、それ以外の部分で問題が起きることは少ないです。
  • 依存関係が増えるため、バンドルサイズへの影響を許容できる場合に適します。

react-dropzone-component は注意が必要です。

  • コアライブラリ(react-dropzone)のラッパーであるため、コアの更新に追従できないリスクがあります。
  • 内部でさらに別のライブラリ(Dropzone.js)に依存している場合、依存の連鎖が複雑になります。
  • 新規プロジェクトでは、ラッパーを使わずコアを直接使う方が長期的な保守性が高い傾向にあります。

📊 比較サマリー

特徴react-dropzonereact-dropzone-uploaderreact-dropzone-component
UI 提供❌ なし (Headless)✅ あり (内置)✅ あり (ラッパー)
アップロード処理⚙️ 手動実装🚀 自動 (設定のみ)⚙️ イベント処理
カスタマイズ性🎨 最高🔧 中 (Styles props)🔧 中 (CSS クラス)
進行状況表示⚙️ 手動実装✅ 自動表示⚙️ 実装による
依存関係最小限中程度多 (ラッパー + 本体)
推奨ユース大規模・カスタム UI小規模・迅速な実装レガシー維持・限定利用

💡 結論:どれを選ぶべきか

react-dropzone を選ぶべき時 プロフェッショナルなフロントエンド開発において、最も推奨されるのは react-dropzone です。UI を完全にコントロールでき、依存関係が最小限であるため、アプリケーションのアーキテクチャを汚しません。進行状況バーやアップロードロジックは、アプリケーションの仕様に合わせて自由に実装できます。長期的なメンテナンス性を最優先する場合は、これ一択です。

react-dropzone-uploader を選ぶべき時 「とにかく早くアップロード機能を実装したい」「進行状況バーなどの UI を自作するリソースがない」という場合に有効です。バックエンドとの連携設定を最小限のプロップスで済ませられるため、プロトタイプや小規模ツール、管理画面の内部機能などで力を発揮します。

react-dropzone-component を選ぶべき時 基本的には新規プロジェクトでの採用は推奨されません。既存のプロジェクトで既に導入されており、移行コストが高い場合の維持目的、あるいは特定の設定ファイル駆動のワークフローが絶対に必要な場合に限定して検討します。コアライブラリである react-dropzone を直接使う方が、技術的負債を減らせます。

最終的には、**「UI を誰が作るか」**という問いに答えることで選定が定まります。自分たちが作るなら react-dropzone、ライブラリに任せるなら react-dropzone-uploader です。

選び方: react-dropzone vs react-dropzone-uploader vs react-dropzone-component

  • react-dropzone:

    プロジェクトのデザインシステムが既に確立されており、アップロード UI を完全にカスタマイズする必要がある場合に選択します。ヘッドレスなフックを提供するため、ビジネスロジックと UI を完全に分離したいアーキテクチャに最適です。コミュニティが最も大きく、長期的なメンテナンスが保証されています。

  • react-dropzone-uploader:

    アップロードの進行状況表示、サーバーへの送信ロジック、エラー処理をライブラリ側で完結させたい場合に選択します。バックエンドとの連携設定を最小限に抑えたい小規模なプロジェクトや、プロトタイプ開発でスピードを重視する際に有効です。

  • react-dropzone-component:

    すでに存在するラッパーコンポーネントの設定ファイルだけで素早く実装したい場合に限って検討します。ただし、メンテナンス頻度がコアライブラリより低いため、長期プロジェクトでは依存リスクを評価する必要があります。既存のレガシーコードで採用されている場合の維持目的に適しています。

react-dropzone のREADME

react-dropzone logo

react-dropzone

npm Tests codecov Open Collective Backers Open Collective Sponsors Gitpod Contributor Covenant

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/.

Installation

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

Usage

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>
  )
}

Dropzone Props Getters

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.

Refs

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()

Testing

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.

Caveats

Required React Version

React 16.8 or above is required because we use hooks (the lib itself is a hook).

File Paths

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.

Not a File Uploader

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.

Using <label> as Root

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>
  )
}

Using open() on Click

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>
  )
}

File Dialog Cancel Callback

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}).

File System Access API

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:

  1. The users will not be able to select directories
  2. It requires the app to run in a secure context
  3. In Electron, the path may not be set (see #1249)

Supported Browsers

We use browserslist config to state the browser support for this lib, so check it out on browserslist.dev.

Need image editing?

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

Backers

Support us with a monthly donation and help us continue our activities. [Become a backer]

Sponsors

Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor]

Hosting

react-dropzone.js.org hosting provided by netlify.

Contribute

Checkout the organization CONTRIBUTING.md.

License

MIT