react-dropzone

repository·master·Indexed 27 days ago

https://github.com/react-dropzone/react-dropzone

A React hook and component library for creating HTML5-compliant drag-and-drop zones for files. It provides the useDropzone hook and a Dropzone wrapper component using a render prop pattern. Features include support for file size and type validation, File System Access API integration, and compatibility with React 18+.

Tokens
5.6K
Snippets
16
Records
28
Agent score
94%

What's inside react-dropzone

  1. Accessing the Camera on Android

    master

    On Android 13+, Chrome and Edge may hide the camera option if the accept attribute only includes image/video types. To force the camera option, pass the capture attribute through getInputProps().

    // Camera only (no gallery/files)
    <input {...getInputProps({capture: "environment"})} />
  2. Access file contents with FileReader API

    master

    To read the actual contents of the dropped files, use the standard browser FileReader API within your onDrop callback.

    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>
      );
    }
  3. Use the useDropzone hook

    master

    The useDropzone hook is the primary way to create a drag 'n' drop zone. You bind the returned props to a container element and a hidden input element to enable drag-and-drop, click-to-select, and keyboard access.

    Important Type Note: Due to a breaking change, FileWithPath now requires path and relativePath. Because the onDrop and onDropAccepted callbacks provide standard File objects, you should type your handler arguments as File[] rather than FileWithPath[] to avoid type errors.

    import React from "react";
    import {useDropzone} from "react-dropzone";
    
    function MyDropzone() {
      const {getRootProps, getInputProps} = useDropzone({
        onDrop: acceptedFiles => {
          // Do something with the files, e.g. upload to a server
          console.log(acceptedFiles);
        }
      });
    
      return (
        <div {...getRootProps()}>
          <input {...getInputProps()} />
          <p>Drag 'n' drop some files here, or click to select files</p>
        </div
      );
    }
  4. Changing Props Before open()

    master

    Because open() must run synchronously within a user gesture, updating a prop (like accept) and calling open() in the same handler will result in open() using the previous render's props.

    Best Practice: Render one dropzone per set of props to ensure the correct configuration is applied immediately when open() is called.

    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>
      );
    }
  5. Test components using react-dropzone

    master

    Because react-dropzone uses asynchronous callbacks for drag-and-drop events, you should use @testing-library/react and wrap event triggers in act() to ensure tests are reliable. Note that Enzyme is not supported.

    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"]
        }
      };
    }
  6. Install react-dropzone via npm or yarn

    master

    Install react-dropzone using your preferred package manager. The package ships as ESM and CommonJS with TypeScript types included, and is compatible with modern bundlers like Vite, webpack, and Rspack.

    npm install react-dropzone

    or:

    yarn add react-dropzone
  7. Using <label> as the Root Element

    master

    Using a <label> as the root element causes the file dialog to open twice because <label> natively forwards clicks to its child <input>. To prevent this, set noClick: true in the useDropzone configuration.

    import React, {useCallback} from "react";
    import {useDropzone} from "react-dropzone";
    
    function MyDropzone() {
      const {getRootProps, getInputProps} = useDropzone({noClick: true});
    
      return (
        <label {...getRootProps()}>
          <input {...getInputProps()} />
        </label>
      );
    }
  8. Using open() on Click

    master

    If you trigger the open() method from a button inside the dropzone, the dialog may open twice due to event bubbling. To prevent this, set noClick: true on the root element.

    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>
      );
    }
  9. Implement a custom validator

    master

    You can provide a validator function in DropzoneOptions to perform custom checks (e.g., checking image dimensions or file content). The validator can be async.

    While an async validator is running, DropzoneState.isProcessing will be true. The onDrop callbacks will only fire once the validator settles.

    Note: Validators do not run during the dragover phase because file names/sizes are not available until the drop occurs; the state will be isDragUnknown during dragging if a validator is configured.

  10. Use the Dropzone wrapper component

    master

    Alternatively, you can use the Dropzone wrapper component which uses a render prop pattern to provide the dropzone property getters.

    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>;