Skip to content
react-mediadrop
Esc
navigateopen⌘Jpreview
On this page

useMediaDrop

Full API reference for the useMediaDrop hook

import { useMediaDrop } from "react-mediadrop";

Headless hook. No prebuilt component — you own the markup.

Options

function useMediaDrop(options?: {
	restrictions?: MediaDropRestrictions;
	validator?: MediaDropValidator;
	noClick?: boolean; // disable click-to-open on the root
	noKeyboard?: boolean; // disable Space/Enter-to-open and focus tracking
	noDrag?: boolean; // disable the root's drag/drop handling
	transport?: UploadTransport; // opt into upload — see below
	concurrency?: number; // max uploads in flight at once. Default 1.
	retries?: number; // retries after the first attempt. Default 0.
	retryDelays?: number[]; // backoff per retry.
	cancelGraceMs?: number; // force-free a slot after cancel. Default 5000.
}): UseMediaDropResult;

Return value

Field Type Notes
files / acceptedFiles / rejectedFiles MediaDropFile[] Every file added, filtered by status.
isDragActive / isDragAccept / isDragReject boolean Per-dropzone drag state — best-effort, see Core concepts.
isFocused boolean Root element has keyboard focus. Always false when noKeyboard is set.
isDragGlobal boolean A file drag is happening anywhere on the document, not just this root.
removeFile(id) / clearFiles() () => void Mutate the file list. Cancels any in-flight upload for removed files.
open() () => void Imperatively open the native file picker.
getRootProps(arg?) () => RootProps Drag/drop + click/keyboard handlers, role, tabIndex.
getInputProps(arg?) () => InputProps Hidden <input type="file"> props.

Passing transport additionally returns uploadFile/uploadAll/ cancelUpload/cancelAllUploads/retryUpload — see Upload. Without transport, none of it exists on the returned object, and TypeScript won’t let you call it.

getRootProps/getInputProps

Both accept and pass through arbitrary HTML attributes alongside the recognized handlers — useful for aria-label/aria-describedby/ className/id/data-*. A consumer-supplied style on getInputProps() is merged, but display: none always wins — it can’t be overridden, since the hidden native input is load-bearing for click-to-open.

<div
	{...getRootProps({
		"aria-label": "File upload dropzone",
		className: "dropzone",
	})}
>
	<input {...getInputProps({ "aria-hidden": "true" })} />
</div>

They compose with your own handlers — yours always runs first:

<div {...getRootProps({ onDrop: (e) => console.log("also dropped", e) })}>

If your handler calls event.stopPropagation(), the hook’s own handling for that event is skipped. This is the one supported way to override built-in behavior.

Click-to-open and keyboard activation

By default, getRootProps() makes the root element click- and keyboard-activatable: clicking anywhere in the root, or pressing Space/Enter while it’s focused, opens the native file picker — the same thing open() does programmatically.

  • noClick: true disables click-to-open (keep open() for a manual “Choose files” button instead).
  • noKeyboard: true disables Space/Enter-to-open, removes tabIndex from the returned props, and stops tracking isFocused.
  • noDrag: true disables drag/drop handling on the root entirely — the input and click-to-open still work.

If you render your own “Choose files” button inside the root element, stop its click from bubbling to the root, or click-to-open fires a second time:

<div {...getRootProps()}>
	<input {...getInputProps()} />
	<button
		type="button"
		onClick={(event) => {
			event.stopPropagation();
			open();
		}}
	>
		Choose files
	</button>
</div>

Things to get right

  • Don’t wrap the returned <input> in display: none yourself and expect open() to fail — getInputProps() already hides it and open() calls .click() on it programmatically, which works through display: none in all evergreen browsers.
  • The hook never touches window/document during render, so it’s SSR-safe. isDragGlobal’s document listeners are registered inside a useEffect (client-only) and removed on unmount. You can render it on the server without guards.
  • The engine backing one useMediaDrop() call is created once for that component instance’s lifetime. Changing restrictions/validator props after mount changes future drag-acceptance previews but does not retroactively re-validate files already in files.
  • Multiple useMediaDrop() calls on the same page are independent and safe — see Core concepts.
  • There’s no dashboard/progress UI to import — every list item, remove button, progress bar, and status message is yours to build.

Was this page helpful?