File Upload
A React hook for handling file uploads with sensible defaults.
Key Features
- File type validation, supporting both file extensions (
.pdf) and MIME types (image/png,image/*) - Maximum file size validation
- Maximum file count and total upload size validation
- Custom validation option for files
- Async upload handling
- Exposes imperative handler API for the
input
Install
You can directly install this into your codebase using shadcn:
pnpx shadcn@latest add https://i11-registry.vercel.app/r/use-file-upload.jsonParameters
| Parameter | Type | Description |
|---|---|---|
options | FileUploadOptions | Optional configuration object |
type FileUploadOptions<TError> = {
ref?: Ref<FileUploadHandle>;
accept?: FileAccept[];
maxFiles?: number;
maxFileSize?: number;
maxTotalSize?: number;
validate?: (files: File[]) => TError | undefined | null;
onChange?: (files: File[], reset: () => void) => void | Promise<void>;
onError?: (error: unknown) => TError | undefined;
};
type FileUploadHandle = {
openFilePicker: () => void;
};
type FileAccept = FileMimeType | FilenameExtension;Returns
| Name | Type | Description |
|---|---|---|
state | FileUploadState | Current upload state |
isIdle | boolean | Whether no upload has started |
isPending | boolean | Whether files are currently being processed |
isSuccess | boolean | Whether upload completed successfully |
isError | boolean | Whether upload failed |
props | FileInputProps | Props for the file input element |
reset | () => void | Clears selected files and resets state |
openFilePicker | () => void | Opens the native file picker |
handleChange | (files: FileList | null) => Promise<void> | Handles file validation and processing |
type FileUploadState<TError extends BaseFileUploadError = BaseFileUploadError> = {
status: "idle" | "pending" | "success" | "error";
files: File[];
error: TError | null;
};
type FileInputProps = {
type: "file";
accept: string | undefined;
ref: RefObject<HTMLInputElement | null>;
multiple: boolean;
};State Status Values
| Status | Description |
|---|---|
"idle" | No files selected |
"pending" | Files are being validated or uploaded |
"success" | Upload completed successfully |
"error" | Upload failed |
Basic Usage
function FileUploader() {
const fileUpload = useFileUpload({
accept: ["image/*"],
maxFileSize: 2 * MB_IN_BYTES,
onError={(e) => {
const description = e instanceof Error ? DEFAULT_EDITOR_ERROR_MESSAGE : toErrorRecord(e).message;
toast.error("Oops", { description, duration: 15 * SECOND_IN_MS, closeButton: true });
}}
onChange: async (files, reset) => { /* image upload logic */},
});
return <input {...fileUpload.props} onChange={(e) => fileUpload.handleChange(e.target.files)} />;
}Imperative Handle Usage
The hook exposes an imperative handle for calling functions from other components:
function FileUploader() {
const fileUploadRef = useRef<FileUploadHandle>(null);
const fileUpload = useFileUpload({
ref: fileUploadRef,
onChange: async (files) => {
console.log(files);
},
});
return (
<>
<input hidden {...upload.props} onChange={(e) => upload.handleChange(e.target.files)} />
<button onClick={() => imageUploadRef.current?.openFilePicker()}>Select Files</button>
</>
);
}Custom Validation Logic
Use the validate callback for domain-specific validation. Return an error object to reject the upload.
useFileUpload({
validate(files) {
return files.some((file) => file.name.endsWith(".exe"))
? { message: "Executable files are not allowed." }
: null;
},
});Async Uploads
The onChange callback supports async operations.
useFileUpload({
async onChange(files) {
const formData = new FormData();
for (const file of files) formData.append("files", file);
await fetch("/api/upload", { method: "POST", body: formData });
},
});The hook automatically enters the "pending" state while the promise is running.
Resetting Uploads
The hook provides a reset function that clears:
- Upload state
- Selected files
- Input value
const upload = useFileUpload();
upload.reset();The same reset function is also provided to onChange.
useFileUpload({
async onChange(files, reset) {
await uploadFiles(files);
reset();
},
});Error Handling
Thrown errors from onChange automatically transition the hook into an error state.
useFileUpload({
async onChange() {
throw new Error("Upload failed.");
},
});You can customize the error formatting with onError:
useFileUpload({
onError(error) {
return {
message: "Something went wrong.",
originalError: error,
};
},
});Options Config
| Option | Type | Default |
|---|---|---|
ref | FileUploadHandle | - |
accept | FileAccept[] | - |
maxFiles | number | 10 |
maxFileSize | number | 30 MB |
maxTotalSize | number | 300 MB |
validate | (files: File[]) => TError | null | - |
onChange | (files: File[], reset: () => void) => void | Promise<void> | - |
onError | (error: unknown) => TError | undefined | - |
Notes
React Compiler Assumption
The hook does not memoize returned callbacks or values internally. It assumes usage with the React Compiler or equivalent render optimizations.