Root
Holds the file list and hands it to every child through context. One required prop: upload.
import { Root } from "uplofile";Usage
Every variant below is the same component. Switch tabs to see what each prop changes.
import { Root } from "uplofile";export function Uploader({ children }: { children: React.ReactNode }) {return (<Rootupload={async (file) => {const body = new FormData();body.append("file", file);const response = await fetch("/api/upload", { method: "POST", body });if (!response.ok) throw new Error("Upload failed");return response.json(); // { url, id?, meta?, previewUrl? }}}>{children}</Root>);}
Props
uploadrequired(file, signal, setProgress?) => Promise<UploadResult>Your transport. Resolve with
{ url, id?, meta?, previewUrl? }; throw to mark the item errored and enable retry.—
removeMode"optimistic" | "strict"Optimistic drops the item at once and restores it if
onRemove rejects. Strict waits for the promise."optimistic"
beforeUpload(items, state) => boolean | Array<{ uid, valid, meta?, id?, reason? }>Gate or enrich files before transport. Async allowed. Return per-file verdicts to reject some and keep the rest;
state carries prevItems, remaining, maxCount, and accept.—
onRemove(item, signal) => Promise<unknown>Server-side delete. Pair with
removeMode. If it throws or rejects, the item stays (or is restored, in optimistic mode) at status: "done" with error set to the failure message; a new removal attempt clears it.—
initialInitialItem[] | Promise<InitialItem[]>Files already on your server. Each item needs
uid, name, and url; id and meta are optional. If you pass a promise, you own its rejection — Root does not attach a .catch.[]
onChange(items: UploadFileItem[]) => void | Promise<void>Fires on every list change, where you can sync form state.
—
onLoadingChange(isLoading: boolean) => voidTrue while
initial is still resolving.—
acceptstring·multipleboolean·maxCountnumberPassed straight to the file input.
maxCount is also readable inside beforeUpload."image/*"
true
—
true
—
namestring·disabledbooleanThe field name for
HiddenInput, and a blanket interaction lock."image"
false
false
Ref API
A ref reaches the same methods from outside the context. It is how you make a whole page a drop target while children still read state.
import { useRef } from "react";import { Preview, Root, type UplofileRootRef } from "uplofile";const upload = async (file: File) => ({ url: URL.createObjectURL(file) });export function PageDropTarget() {const ref = useRef<UplofileRootRef>(null);return (<mainonDrop={(event) => ref.current?.onDrop?.(event)}onDragOver={(event) => ref.current?.onDragOver?.(event)}><Root ref={ref} upload={upload}><Preview /></Root></main>);}
getItems()read
setItems(next)write
onDrop(e)event
onDragOver(e)event
openFileDialog()action
actionscancel · remove · retry
isLoadingboolean