HapplyUI

Hooks

useFileUpload

A React hook that manages file uploads with GCS presigned URLs via Uppy. Handles progress tracking, retry logic, file state management, and provides dropzone integration helpers.


Installation

bunx @happlyui/cli@latest add use-file-upload

Dependencies

npm packages

  • @uppy/core
  • @uppy/xhr-upload

Usage

import { useFileUpload } from "@/hooks/use-file-upload"

const upload = useFileUpload({
  endpoint: '/api/assets/pre-signed',
  providerId: workspace.id,
  assetType: 'attachment',
  headers: () => ({ Authorization: `Bearer ${token}` }),
})

Examples

Document Upload with Dropzone

Combine useFileUpload with FileUpload.Dropzone and FileCard.Item to create a full upload experience with minimal boilerplate.

import { useFileUpload } from "@/hooks/use-file-upload"
import * as FileUpload from "@/components/ui/file-upload"
import * as FileCard from "@/components/ui/file-card"

function DocumentUploader() {
  const upload = useFileUpload({
    endpoint: '/api/assets/pre-signed',
    providerId: workspace.id,
    assetType: 'attachment',
    headers: () => ({ Authorization: `Bearer ${token}` }),
    maxFiles: 5,
    allowedFileTypes: ['application/pdf'],
    onUploadSuccess: (file) => saveToForm(file.url),
  })

  return (
    <div className="space-y-3">
      <FileUpload.Dropzone
        type="document"
        {...upload.getRootProps()}
        inputProps={upload.getInputProps()}
      />
      {upload.files.map((file) => (
        <FileCard.Item
          key={file.id}
          file={file}
          variant="compact"
          onRemove={() => upload.removeFile(file.id)}
          onRetry={() => upload.retryFile(file.id)}
          onClose={() => upload.removeFile(file.id)}
        />
      ))}
    </div>
  )
}

Logo Upload

Pair useFileUpload with LogoUpload.Item for a complete logo upload experience with avatar preview.

import { useFileUpload } from "@/hooks/use-file-upload"
import * as LogoUpload from "@/components/ui/logo-upload"

function BusinessLogoUploader() {
  const upload = useFileUpload({
    endpoint: '/api/assets/pre-signed',
    providerId: workspace.id,
    assetType: 'logo',
    acl: 'public-read',
    maxFiles: 1,
    allowedFileTypes: ['image/jpeg', 'image/png'],
    maxFileSize: 3 * 1024 * 1024,
    onUploadSuccess: (file) => updateLogo(file.url),
  })

  return (
    <>
      <LogoUpload.Item
        file={upload.files[0]}
        label="Business logo"
        description={
          <>
            <p>Supports JPEG or PNG files (max 3MB).</p>
            <p>Use a horizontal image (16:9). Best size: 1200 × 675 px.</p>
          </>
        }
        onButtonClick={upload.openFilePicker}
      />
      <input {...upload.getInputProps()} />
    </>
  )
}

API Reference

useFileUpload Options

Configuration options passed to the hook.

PropTypeDefaultDescription
endpointstring-The API endpoint for obtaining presigned upload URLs (e.g. '/api/assets/pre-signed').
baseUrlstring''Base URL prepended to the endpoint.
headers() => Record<string, string>-Function returning headers for the presigned URL request (e.g. auth tokens).
providerIdstring-The provider/workspace ID sent to the presigned URL endpoint.
assetTypestring-The asset type sent to the presigned URL endpoint (matches backend AssetType enum).
acl'private' | 'public-read''private'Access control level for the uploaded file.
maxFilesnumber1Maximum number of files allowed.
maxFileSizenumber5242880Maximum file size in bytes (default 5MB).
allowedFileTypesstring[]-Allowed MIME types or file extensions.
onUploadSuccess(file: UploadFile) => void-Called when a file upload completes successfully.
onUploadError(file: UploadFile, error: Error) => void-Called when a file upload fails.
onFileRemove(file: UploadFile) => void-Called when a file is removed from the list.

useFileUpload Return Value

The object returned by the hook.

PropTypeDefaultDescription
filesUploadFile[]-Array of all files with their current upload state.
isUploadingboolean-True if any file is currently uploading.
isDraggingOverboolean-True when a file is being dragged over the dropzone (via getRootProps).
addFiles(files: File[]) => void-Add native File objects to the upload queue.
removeFile(id: string) => void-Remove a file by ID. Cancels upload if in progress.
retryFile(id: string) => void-Retry a failed upload by re-adding the original file.
clearFiles() => void-Remove all files and revoke preview URLs.
openFilePicker() => void-Programmatically open the native file picker dialog.
getInputProps() => InputHTMLAttributes-Returns props to spread on a hidden <input type="file">.
getRootProps() => { onDrop, onDragOver, onDragEnter, onDragLeave }-Returns drag event handlers to spread on a dropzone container. Tracks isDraggingOver state internally.

UploadFile

The file state object tracked by the hook.

PropTypeDefaultDescription
idstring-Unique file identifier (from Uppy).
namestring-Original file name.
sizenumber-File size in bytes.
typestring-MIME type.
progressnumber-Upload progress from 0 to 100.
status'pending' | 'uploading' | 'completed' | 'failed'-Current upload status.
urlstring-Public URL after successful upload.
keystring-GCS storage path/key.
previewstring-Object URL for image file previews (revoked on removal).
errorstring-Error message if upload failed.

Previous
useFileDownload