i11 UIi11 registry

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.json

Parameters

ParameterTypeDescription
optionsFileUploadOptionsOptional 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

NameTypeDescription
stateFileUploadStateCurrent upload state
isIdlebooleanWhether no upload has started
isPendingbooleanWhether files are currently being processed
isSuccessbooleanWhether upload completed successfully
isErrorbooleanWhether upload failed
propsFileInputPropsProps for the file input element
reset() => voidClears selected files and resets state
openFilePicker() => voidOpens 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

StatusDescription
"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

OptionTypeDefault
refFileUploadHandle-
acceptFileAccept[]-
maxFilesnumber10
maxFileSizenumber30 MB
maxTotalSizenumber300 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.