react-dropzone vs react-csv vs react-papaparse vs react-csv-reader vs papaparse
CSV Parsing and File Upload Solutions for React Applications
react-dropzonereact-csvreact-papaparsereact-csv-readerpapaparseSimilar Packages:

CSV Parsing and File Upload Solutions for React Applications

These five packages address CSV file handling and file upload workflows in React applications, but they serve different purposes. papaparse is a powerful vanilla JavaScript CSV parser that works in both browser and Node.js environments. react-csv provides React components specifically for exporting data as CSV files. react-csv-reader offers a React component for reading and parsing uploaded CSV files. react-dropzone is a general-purpose file dropzone component that handles any file type with drag-and-drop support. react-papaparse wraps papaparse functionality in React hooks and components for easier integration.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-dropzone13,336,05611,013341 kB525 days agoMIT
react-csv883,4531,18440.9 kB135-MIT
react-papaparse286,93238177.6 kB563 years agoMIT
react-csv-reader45,30319994.8 kB104 years agoMIT
papaparse013,586271 kB2322 months agoMIT

CSV Parsing and File Upload Solutions for React Applications

When building React applications that handle CSV files, you have several options — but they solve different problems. Some packages focus on parsing CSV data, others on exporting it, and some handle the file upload experience itself. Let's compare how these five packages tackle common CSV workflows.

📥 Parsing CSV Files: Direct vs React-Wrapped

papaparse is the foundation — a vanilla JavaScript library that parses CSV strings and files with extensive configuration.

import Papa from 'papaparse';

Papa.parse(file, {
  header: true,
  complete: (results) => {
    console.log(results.data);
  }
});

react-papaparse wraps papaparse in React hooks for easier state management within components.

import { usePapaParse } from 'react-papaparse';

function CsvUploader() {
  const { readString } = usePapaParse();
  
  const handleFile = (file) => {
    readString(file, {
      header: true,
      complete: (results) => {
        console.log(results.data);
      }
    });
  };
}

react-csv-reader provides a pre-built component that handles both file selection and parsing.

import CsvReader from 'react-csv-reader';

function CsvUploader() {
  const handleFileRead = (data) => {
    console.log(data);
  };

  return <CsvReader onFileLoaded={handleFileRead} />;
}

react-dropzone handles file selection but requires you to add parsing logic separately.

import { useDropzone } from 'react-dropzone';
import Papa from 'papaparse';

function CsvUploader() {
  const onDrop = (acceptedFiles) => {
    acceptedFiles.forEach(file => {
      Papa.parse(file, {
        complete: (results) => console.log(results.data)
      });
    });
  };

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

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drop CSV files here</p>
    </div>
  );
}

react-csv does not support parsing — it only exports data as CSV downloads.

// react-csv has no parsing functionality
// It only provides CSVLink and CSVDownload components for export

📤 Exporting Data as CSV: Built-In vs Manual

react-csv specializes in exporting React data as downloadable CSV files with minimal code.

import { CSVLink } from 'react-csv';

function ExportButton({ data }) {
  return (
    <CSVLink data={data} filename="export.csv">
      Download CSV
    </CSVLink>
  );
}

papaparse can write CSV strings but requires manual download handling.

import Papa from 'papaparse';

function exportData(data) {
  const csv = Papa.unparse(data);
  const blob = new Blob([csv], { type: 'text/csv' });
  const url = URL.createObjectURL(blob);
  // Create and trigger download link manually
}

react-papaparse includes CSV writing through its hook but still needs manual download setup.

import { usePapaParse } from 'react-papaparse';

function ExportButton({ data }) {
  const { unparse } = usePapaParse();
  
  const handleExport = () => {
    const csv = unparse(data);
    // Manual download trigger needed
  };
}

react-csv-reader and react-dropzone do not support CSV export — they focus on file input only.

// Neither react-csv-reader nor react-dropzone provide export functionality
// You would need to combine them with papaparse or react-csv for exports

🎨 UI Customization: Pre-Built vs Build-Your-Own

react-dropzone gives you full control over the upload UI with render props and hooks.

import { useDropzone } from 'react-dropzone';

function CustomDropzone() {
  const { getRootProps, getInputProps, isDragActive } = useDropzone();

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {isDragActive ? (
        <p>Drop the files here ...</p>
      ) : (
        <p>Drag and drop CSV files here</p>
      )}
    </div>
  );
}

react-csv-reader provides a styled component with limited customization options.

import CsvReader from 'react-csv-reader';

function SimpleUploader() {
  return (
    <CsvReader
      onFileLoaded={handleData}
      inputStyle={{ backgroundColor: '#f0f0f0' }}
    />
  );
}

papaparse has no UI — you build the entire file input experience yourself.

import Papa from 'papaparse';

function ManualUploader() {
  const handleFile = (e) => {
    const file = e.target.files[0];
    Papa.parse(file, { complete: (results) => {} });
  };

  return <input type="file" onChange={handleFile} />;
}

react-papaparse provides some pre-built components but encourages custom UI with hooks.

import { usePapaParse } from 'react-papaparse';

function HookBasedUploader() {
  const { readString } = usePapaParse();
  
  return (
    <input
      type="file"
      onChange={(e) => readString(e.target.files[0], {})}
    />
  );
}

react-csv provides styled link components for exports with basic customization.

import { CSVLink } from 'react-csv';

function StyledExport() {
  return (
    <CSVLink
      data={data}
      filename="export.csv"
      className="btn btn-primary"
    >
      Export
    </CSVLink>
  );
}

⚡ Large File Handling: Streaming vs In-Memory

papaparse supports streaming for large files to avoid memory issues.

import Papa from 'papaparse';

Papa.parse(file, {
  chunk: (results) => {
    // Process each chunk separately
    console.log(results.data.length);
  },
  step: (row) => {
    // Process row by row
  },
  worker: true // Use web worker for heavy parsing
});

react-papaparse inherits papaparse's streaming capabilities through its hook.

import { usePapaParse } from 'react-papaparse';

function LargeFileHandler() {
  const { readString } = usePapaParse();

  const handleLargeFile = (file) => {
    readString(file, {
      chunk: (results) => {
        // Process chunks in component
      },
      worker: true
    });
  };
}

react-csv-reader, react-dropzone, and react-csv do not have built-in streaming support — they load files into memory.

// These packages load entire files into memory
// For large files, you would need to integrate papaparse directly

🔧 Configuration and Flexibility

papaparse offers the most configuration options for parsing behavior.

import Papa from 'papaparse';

Papa.parse(file, {
  delimiter: ',',
  newline: '\n',
  quoteChar: '"',
  escapeChar: '"',
  header: true,
  transformHeader: (h) => h.toLowerCase(),
  dynamicTyping: true,
  skipEmptyLines: true,
  encoding: 'utf-8'
});

react-papaparse passes the same configuration options through its hook.

import { usePapaParse } from 'react-papaparse';

function ConfigurableParser() {
  const { readString } = usePapaParse();

  const parseWithConfig = (file) => {
    readString(file, {
      header: true,
      dynamicTyping: true,
      skipEmptyLines: true
    });
  };
}

react-csv-reader has limited configuration focused on basic parsing needs.

import CsvReader from 'react-csv-reader';

function BasicReader() {
  return (
    <CsvReader
      onFileLoaded={handleData}
      parserOptions={{ delimiter: ',' }}
    />
  );
}

react-csv focuses on export configuration rather than parsing.

import { CSVLink } from 'react-csv';

function ConfiguredExport() {
  return (
    <CSVLink
      data={data}
      headers={headers}
      filename="export.csv"
      enclosingCharacter={'"'}
    />
  );
}

react-dropzone configures file acceptance rules rather than CSV parsing.

import { useDropzone } from 'react-dropzone';

function FileAcceptor() {
  const { getRootProps, getInputProps } = useDropzone({
    accept: {
      'text/csv': ['.csv'],
      'application/vnd.ms-excel': ['.xls']
    },
    maxFiles: 1,
    maxSize: 10485760 // 10MB
  });
}

🌱 When to Combine Packages

Real-world applications often need multiple packages working together:

Scenario 1: Upload and Export Dashboard

Use react-dropzone for flexible uploads + papaparse for parsing + react-csv for exports.

import { useDropzone } from 'react-dropzone';
import Papa from 'papaparse';
import { CSVLink } from 'react-csv';

function Dashboard() {
  const [data, setData] = useState([]);

  const onDrop = (files) => {
    Papa.parse(files[0], {
      header: true,
      complete: (results) => setData(results.data)
    });
  };

  const { getRootProps } = useDropzone({ onDrop, accept: {'text/csv': ['.csv']} });

  return (
    <div>
      <div {...getRootProps()}>Drop CSV here</div>
      <CSVLink data={data} filename="processed.csv">Export</CSVLink>
    </div>
  );
}

Scenario 2: Simple CSV Import Form

Use react-csv-reader for quick setup with minimal code.

import CsvReader from 'react-csv-reader';

function SimpleImport() {
  const handleData = (data) => {
    // Process imported data
  };

  return <CsvReader onFileLoaded={handleData} />;
}

Scenario 3: React-Native CSV Workflow

Use react-papaparse for hook-based state management throughout your app.

import { usePapaParse } from 'react-papaparse';

function CsvWorkflow() {
  const { readString, unparse } = usePapaParse();
  const [parsedData, setParsedData] = useState([]);

  const handleUpload = (file) => {
    readString(file, {
      header: true,
      complete: (results) => setParsedData(results.data)
    });
  };

  const handleExport = () => {
    const csv = unparse(parsedData);
    // Trigger download
  };
}

⚠️ Maintenance and Deprecation Status

As of current documentation:

  • papaparse — Actively maintained, stable API, widely used in production
  • react-csv — Maintained but updates are infrequent; still functional for export needs
  • react-csv-reader — Limited recent activity; consider for simple use cases but evaluate alternatives for critical features
  • react-dropzone — Actively maintained with regular updates and strong community support
  • react-papaparse — Maintained but depends on papaparse updates; verify compatibility with latest papaparse versions

💡 Tip: For new projects requiring CSV parsing, start with papaparse directly. Add React wrappers only if they simplify your specific workflow.

📌 Summary Table

PackagePrimary UseReact-SpecificParsingExportCustom UI
papaparseCSV parsing❌✅✅ (manual)❌
react-csvCSV export✅❌✅Basic
react-csv-readerCSV import✅✅❌Limited
react-dropzoneFile upload✅❌ (add separately)❌Full
react-papaparseCSV parsing✅✅✅ (manual)Moderate

💡 Final Recommendation

Think about your actual workflow needs:

  • Need to parse CSV files? → Start with papaparse for maximum control, or react-papaparse for React hook integration
  • Need to export data as CSV? → Use react-csv for simple React component exports
  • Need a quick upload form? → Use react-csv-reader for minimal setup
  • Need custom drag-and-drop UI? → Use react-dropzone and add parsing separately
  • Building a complete CSV workflow? → Combine react-dropzone + papaparse + react-csv

The key insight: These packages solve different parts of the CSV workflow. Don't expect one package to do everything. Choose based on whether you need parsing, exporting, or file upload UI — then combine them as needed for your specific use case.

How to Choose: react-dropzone vs react-csv vs react-papaparse vs react-csv-reader vs papaparse

  • react-dropzone:

    Choose react-dropzone if you need flexible drag-and-drop file upload functionality with extensive customization options. It handles any file type and provides hooks for building custom upload UIs. Ideal when you need full control over the upload experience or when CSV is just one of many file types your app accepts.

  • react-csv:

    Choose react-csv if your primary need is exporting data from React applications as downloadable CSV files. It provides simple components like CSVLink and CSVDownload that integrate directly with your React component tree. Ideal for admin dashboards, reports, or any feature where users need to export table data.

  • react-papaparse:

    Choose react-papaparse if you want papaparse's parsing power wrapped in React hooks and components for easier state management. It simplifies integration by providing usePapaParse hook and pre-built components. Best for React projects that need CSV parsing without manually wiring papaparse into your component lifecycle.

  • react-csv-reader:

    Choose react-csv-reader if you need a ready-made React component for users to upload and parse CSV files with minimal setup. It combines file input handling with CSV parsing in a single component. Best for simple upload forms where you don't need custom dropzone styling or advanced file handling features.

  • papaparse:

    Choose papaparse if you need a robust, framework-agnostic CSV parser with extensive configuration options for parsing, writing, and streaming large files. It works in both browser and Node.js environments and gives you full control over parsing behavior. Best for projects that need CSV processing without React-specific bindings or when you want to build custom upload components.

README for react-dropzone

react-dropzone logo

react-dropzone

npm Tests coverage 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. react-dropzone ships as ESM and CommonJS with TypeScript types included, and works with any modern bundler (Vite, webpack, Rspack, etc.).

npm install 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"
    // ...and any other props
  })}
/>

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

  return (
    <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();
  return (
    <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 18 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.

Tauri (and other native webviews)

Inside a Tauri webview, OS file drops are intercepted by the native window layer before the webview sees them, so the browser's drag 'n' drop events never fire and the dropzone appears to ignore drops. Disable Tauri's native handling to let react-dropzone work unchanged: set dragDropEnabled: false (Tauri v2) or fileDropEnabled: false (Tauri v1) in tauri.conf.json. If you instead need the absolute paths of dropped files, keep native handling on and drive your own state from Tauri's drag-drop event. See the Using with Tauri guide and #1316.

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.

Paste to Upload

Pasting files into the dropzone is supported out of the box: when the dropzone (or a focused child, e.g. a <textarea>) has focus and the user pastes files with Ctrl/Cmd+V - for instance a screenshot copied to the clipboard - those files run through the same accept, size and validator checks and the same onDrop/onDropAccepted/onDropRejected callbacks as a drop. Pastes that carry no files (plain text and the like) are ignored and left untouched, so pasting into inputs keeps working.

The paste is only received when the dropzone has focus, so pair it with autoFocus or let the user click/tab into it first. To disable paste handling, set noPaste:

import React from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const {getRootProps, getInputProps} = useDropzone({noPaste: true});

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop or click - pasting is disabled</p>
    </div>
  );
}

Using <label> as Root

If you use <label> as the root element, the file dialog will be opened twice; see #1107 and #1432 why. A <label> natively forwards clicks to the <input> it wraps, so the dialog opens once from that and once from our own click handler. 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>
  );
}

Changing Props Before open()

Browsers only let you open the file dialog from within a short window after a user gesture (a "transient user activation"). Because of this, open() must run synchronously in the same event that the user triggered - it cannot wait for React to re-render.

So if you update a prop (e.g. accept) and call open() in the same handler, open() runs against the previous render, before the new prop is applied. Some browsers (notably Safari) may also drop the file dialog entirely, so it appears to require a second click. See #1188.

// 🚫 open() runs with the old `accept` - and may not open at all on Safari
function MyDropzone() {
  const [accept, setAccept] = useState({"image/*": []});
  const {getRootProps, getInputProps, open} = useDropzone({accept, noClick: true});

  const pickPdfs = () => {
    setAccept({"application/pdf": []}); // applied on the next render
    open(); // still uses {"image/*": []}
  };

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <button type="button" onClick={pickPdfs}>
        Pick PDFs
      </button>
    </div>
  );
}

Instead, render one dropzone per set of props (each with its own open) and call the right one, so no state change has to land before opening:

function MyDropzone() {
  const images = useDropzone({accept: {"image/*": []}, noClick: true});
  const pdfs = useDropzone({accept: {"application/pdf": []}, noClick: true});

  return (
    <div {...images.getRootProps()}>
      <input {...images.getInputProps()} />
      <input {...pdfs.getInputProps()} />
      <button type="button" onClick={images.open}>
        Pick images
      </button>
      <button type="button" onClick={pdfs.open}>
        Pick PDFs
      </button>
    </div>
  );
}

Camera Option Missing on Android

On Android 13+, Chrome and Edge open the native Android photo picker whenever a file input's accept attribute resolves to only image and/or video types (e.g. accept={{"image/*": []}}). That picker only lists the gallery and files - it has no "take a photo"/camera action - so the camera appears to be missing. See #1417.

This is browser/OS behavior, not something the lib controls, and it's unrelated to how the <input> is styled: a plain <input type="file"> with no accept (or one that also allows a non-media type) falls back to the older chooser that does include the Camera app.

If you need the camera, use the capture attribute, which you can pass straight through getInputProps():

// Camera only (no gallery/files)
<input {...getInputProps({capture: "environment"})} />

To offer both the camera and the gallery, render one dropzone per input (each with its own open) and let the user pick which one to trigger:

import React from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone({onDrop}) {
  const gallery = useDropzone({accept: {"image/*": []}, noClick: true, onDrop});
  const camera = useDropzone({accept: {"image/*": []}, noClick: true, onDrop});

  return (
    <>
      <button type="button" onClick={gallery.open}>
        Choose from gallery
      </button>
      <input {...gallery.getInputProps()} />

      <button type="button" onClick={camera.open}>
        Take a photo
      </button>
      <input {...camera.getInputProps({capture: "environment"})} />
    </>
  );
}

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)
  4. Some browsers/configurations block showOpenFilePicker() and reject with a NotAllowedError (e.g. Microsoft Edge for Business or other restrictive enterprise/security policies). When this happens we automatically fall back to the native <input> so the file dialog still opens, provided you render one via getInputProps() (see #1429)

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