useDropzone
WIPHeadless drag-and-drop and file-picker hook — prop getters, isDragging, accept filtering.
useDropzone is a headless hook that wires drag-and-drop and the native file picker to the
upload store. It ships no markup and no styling — you get prop getters for a drop target and
a hidden <input type="file">, plus a live isDragging flag. Everything visual is yours.
import { useDropzone } from '@gentleduck/upload/react'
function Drop() {
const { getRootProps, getInputProps, isDragging, open } = useDropzone({
purpose: 'attachment',
accept: 'image/*,.pdf',
})
return (
<div {...getRootProps()} data-active={isDragging}>
<input {...getInputProps()} />
<p>Drop files here or click to browse</p>
<button type="button" onClick={open}>Choose files</button>
</div>
)
}
Dropped or selected files are dispatched to the store as addFiles (or handed to your own
onFiles). The hook reads the store from <UploadProvider> context — or pass an explicit
store.
Options
useDropzone<P>({
purpose: P // forwarded to the addFiles command (required)
meta?: Record<string, unknown> // caller metadata forwarded to backend API calls
accept?: string // "image/*,.pdf" — filters files and sets input accept
multiple?: boolean // default true; false keeps only the first file
disabled?: boolean // default false; no drop, no click-to-open
noClick?: boolean // default false; don't open the picker on root click
onFiles?: (files: File[]) => void // override the default addFiles dispatch
store?: UploadStore // dispatch target; falls back to context
})
accept matching
accept is a comma-separated list of tokens, matched like the native attribute:
| Token | Matches |
|---|---|
image/png | exact MIME type |
image/* | any type in the group |
.pdf | filename extension (case-insensitive) |
A file passes if it matches any token. An absent or empty accept allows everything.
Return value
| Field | Type | Purpose |
|---|---|---|
isDragging | boolean | True while files are dragged over the root |
open | () => void | Programmatically open the file picker |
inputRef | RefObject | Ref to the hidden input (already attached by getInputProps) |
getRootProps | (extra?) => props | Spread onto the drop target |
getInputProps | (extra?) => props | Spread onto the hidden <input type="file"> |
Both getters merge any handlers/styles you pass, calling yours first — so
getRootProps({ onClick }) still runs your onClick before opening the picker.
Behavior notes
- Nested children don't flicker — an internal depth counter tracks
dragenter/dragleave, soisDraggingstays true while hovering child elements. dropis enabled — the root'sdragover/dropcallpreventDefault, so the browser won't navigate away when a file is dropped.- Re-selecting the same file works — the input's value is reset after each change, so picking the same file twice still fires.
- Filtering happens before dispatch — files rejected by
acceptnever reach the store; withmultiple: falseonly the first accepted file is dispatched.
Custom handling
Skip the store entirely and receive the raw file list:
const { getRootProps, getInputProps } = useDropzone({
purpose: 'attachment',
onFiles: (files) => console.log('picked', files),
})
Pure helpers
The matching logic is exported for reuse and testing:
import { fileMatchesAccept, matchesAcceptToken, selectFiles } from '@gentleduck/upload/react'
selectFiles(files, { accept: 'image/*', multiple: false }) // filtered, first-only
fileMatchesAccept(file, 'image/*,.pdf') // boolean
Next
- useUploader — render the items the dropzone adds.
- UploadProvider — the store context the hook reads.