useFileUpload
function useFileUpload<E extends HTMLElement, CustomError = never>(
options: FileUploadOptions<E, CustomError> = {}
): Readonly<FileUploadHookReturnValue<E, CustomError>>;
The useFileUpload
hook can be used to upload files to the browser in
different formats to be previewed <img>
, <video>
, <embed>
, etc tags.
However, it can also be used to upload the files as an ArrayBuffer
and then
uploaded to a server.
Example Usage
This example will show how to handle simple validation and preview uploaded
files. Only the Demo
files are editable.
Server Uploads
If the files should be uploaded to the server instead of previewed in the
browser, it is recommended to set the getFileParser
option to always
"readAsArrayBuffer"
. Here is a small example for how you might want to
upload files to the server using this hook.
This example also showcases some other UI that can be rendered by utilizing the data returned by this hook.
Parameters
options
(optional) - an object with the following definition:
interface FileUploadOptions<E extends HTMLElement, CustomError = never>
extends FileUploadHandlers<E>,
FileValidationOptions {
/**
* Setting this value to a number greater than `0` will update the browser
* upload process to queue the uploads in chunks instead of all at once. This
* can help prevent the browser from freezing if dealing with large files that
* are being converted to data urls.
*
* @defaultValue `-1`
*/
concurrency?: number;
/** {@inheritDoc FilesValidator} */
validateFiles?: FilesValidator<CustomError>;
/** {@inheritDoc GetFileParser} */
getFileParser?: GetFileParser;
}
export interface FileUploadHandlers<E extends HTMLElement> {
onDrop?: DragEventHandler<E>;
onChange?: ChangeEventHandler<HTMLInputElement>;
}
export interface FileValidationOptions {
/**
* If the number of files should be limited, set this value to a number
* greater than `0`.
*
* Note: This still allows "infinite" files when set to `0` since the
* `<input>` element should normally be set to `disabled` if files should not
* be able to be uploaded.
*
* @defaultValue `-1`
*/
maxFiles?: number;
/**
* An optional minimum file size to enforce for each file. This will only be
* used when it is greater than `0`.
*
* @defaultValue `-1`
*/
minFileSize?: number;
/**
* An optional maximum file size to enforce for each file. This will only be
* used when it is greater than `0`.
*
* @defaultValue `-1`
*/
maxFileSize?: number;
/**
* An optional list of extensions to enforce when uploading files.
*
* Note: The extensions and file names will be compared ignoring case.
*
* @example Only Allow Images
* ```ts
* const extensions = ["png", "jpeg", "jpg", "gif"];
* ```
*/
extensions?: readonly string[];
/** {@inheritDoc IsValidFileName} */
isValidFileName?: IsValidFileName;
/**
* An optional total file size to enforce when the {@link maxFiles} option is
* not set to `1`.
*
* @defaultValue `-1`
*/
totalFileSize?: number;
}
Returns
An object with the following definition:
export interface FileUploadState<CustomError = never> {
/**
* All the files that have been validated and are either:
* - pending upload
* - uploading
* - complete
*
* Each key in this object is the {@link BaseFileUploadStats.key} generated
* once the upload starts pending.
*/
stats: Readonly<Record<string, Readonly<FileUploadStats>>>;
/**
* A list of validation errors that have occurred before starting the upload
* process.
*
* @see {@link FileAccessError}
* @see {@link TooManyFilesError}
* @see {@link FileValidationError}
*/
errors: readonly FileValidationError<CustomError>[];
}
export interface FileUploadHookReturnValue<
E extends HTMLElement = HTMLElement,
CustomError = never,
> extends FileUploadActions,
Required<FileUploadHandlers<E>> {
errors: readonly FileValidationError<CustomError>[];
/**
* A list of all the {@link FileUploadStats}.
*
* @see {@link getSplitFileUploads} for separating by status
*/
stats: readonly Readonly<FileUploadStats>[];
/**
* The total number of bytes for all the files that exist in the
* {@link stats} list.
*/
totalBytes: number;
/**
* The total number of files in the {@link stats} list.
*/
totalFiles: number;
/**
* An `accept` string that can be passed to the {@link FileInput} component
* when the {@link FileValidationOptions.extensions} list has been provided to
* limit which files the OS will _attempt_ to allow access to.
*
* @example Simple example
* ```ts
* const extensions = ['pdf', 'docx', 'ppt'];
* const { accept } = useFileUpload({ extensions, ...others });
*
* expect(accept).toBe("*.pdf,*.docx,*.ppt")
* ```
*
* @defaultValue `"*"`
*/
accept: string;
}
export interface FileUploadActions {
/**
* Reset everything related to uploads ensuring that all file readers have
* been aborted.
*/
reset: () => void;
/**
* Removes all the errors that exist in state without canceling any of the
* uploads already in progress.
*/
clearErrors: () => void;
/**
* This function is used to cancel pending and uploading files or removing
* completed files.
*
* @param keyOrKeys - A single or list of {@link BaseFileUploadStats.key} to
* remove from state.
*/
remove: (keyOrKeys: string | readonly string[]) => void;
}
Errors
TooManyFilesError
This error will occur when the user attempts to upload more than the total
number of files allowed. The TooManyFilesError
error will contain:
key
- a unique key that can be used as aReact
keyfiles
- a readonly list of File that caused this errorlimit
- the max limit of files allowed
FileSizeError
This error will occur when the user attempts to upload a file that is either:
- less than the
minFileSize
- greater than the
maxFileSize
- greater than the
totalFileSize
when combined with all the other upload file sizes
The FileSizeError
error will contain:
key
- a unique key that can be used as aReact
keyfiles
- a readonly list of File that caused this errortype
- either"min"
,"max"
, or"total"
representing the file size error typelimit
- the total number of bytes related to the file size error type
FileExtensionError
This error will occur when the user attempts to upload a file that does not end with one
of the supported extensions
. The FileExtensionError
error will contain:
key
- a unique key that can be used as aReact
keyfiles
- a readonly list of File that caused this error
GenericFileError
This is a helper error class that is used by the other error types and
probably won't be triggered. The GenericFileError
error will contain:
key
- a unique key that can be used as aReact
keyfiles
- a readonly list of File that caused this error
FileAccessError
This error will occur if the user attempts to drag and drop a file from a shared directory that they do not have access to. This error is very uncommon.
Utils
validateFiles
function validateFiles<CustomError>(
files: readonly File[],
options: FilesValidationOptions
): ValidatedFilesResult<CustomError>;
This is the default implementation for validating files with the
useFileUpload
hook.
getSplitFileUploads
function getSplitFileUploads(
stats: readonly FileUploadStats[]
): SplitFileUploads;
The getSplitFileUploads
can be used to split the stats
returned by the
useFileUpload
hook into the pending
, uploading
, and complete
groups
to create a dynamic UI for file uploads.
Main Usage
import { FileUpload } from "@react-md/core/files/FileUpload";
import { useFileUpload } from "@react-md/core/files/useFileUpload";
import { getSplitFileUploads } from "@react-md/core/files/utils";
function Example() {
const { stats, errors, accept, onChange } = useFileUpload();
const { pending, uploading, completed } = getSplitFileUploads(stats);
return (
<>
<FileUpload accept={accept} onChange={onChange} />
{pending.map(({ key, file, progress, status }) => {
// pretend some UI for each pending item with the provided data
return null;
})}
{uploading.map(({ file, key, progress, status }) => {
// pretend some UI for each uploading item with the provided data
return null;
})}
{complete.map(({ file, key, progress, result, status }) => {
// pretend some UI for each complete item with the provided data
return null;
})}
</>
);
}
Parameters
stats
- a readonly list of objects with the following shape:
export type FileUploadStats =
| ProcessingFileUploadStats
| CompletedFileUploadStats;
export interface ProcessingFileUploadStats extends BaseFileUploadStats {
status: "pending" | "uploading";
}
export interface CompletedFileUploadStats extends BaseFileUploadStats {
status: "complete";
result: FileReader["result"];
}
export interface BaseFileUploadStats {
key: string;
file: File;
progress: number;
}
Returns
An object with the following definition:
export interface SplitFileUploads {
readonly pending: readonly ProcessingFileUploadStats[];
readonly uploading: readonly ProcessingFileUploadStats[];
readonly complete: readonly CompletedFileUploadStats[];
}
isValidFileName
export type IsValidFileName = (
file: File,
extensionRegExp: RegExp | undefined,
extensions: readonly string[]
) => boolean;
export const isValidFileName: IsValidFileName;
This is just the default implementation for checking if a provided file is valid based on the provided extensions.
File Type Checkers
The file type checkers are not guaranteed to be 100% correct and all have the following signature:
type IsFileType = (file: File) => boolean;
Type Guards
The following type guards are provided to determine how to display the errors and all have the following signature:
type IsKnownError<
CustomError extends object,
ExpectedError extends FileValidationError<never>,
> = (error: FileValidationError<CustomError>) => error is ExpectedError;