Skip to content
Files SDK
Esc
navigateopen⌘Jpreview
On this page

Upload Progress

Per-file and aggregate progress bars for in-flight uploads, reading the ambient state of a useFiles instance.

A drop-in progress view. It reads the ambient uploads and progress a useFiles instance exposes, so it lights up for any upload that instance starts — wherever the call originates. Returns null when idle.

"use client";

import { UploadIcon } from "lucide-react";
import type { ChangeEvent } from "react";
import { useEffect, useRef } from "react";

import { Button } from "@/components/ui/button";
import { demoFiles } from "@/lib/demo-files";
import { UploadProgress } from "@/registry/files-sdk/upload-progress/upload-progress";

const Example = () => {
  const files = demoFiles;
  const inputRef = useRef<HTMLInputElement>(null);

  // Read `files` via a ref so this one-shot effect never re-runs on store
  // changes (see the file-list/file-preview note on the loop footgun).
  const filesRef = useRef(files);
  filesRef.current = files;

  // Kick off a sample upload on mount so the progress UI starts populated.
  useEffect(() => {
    const bytes = new Uint8Array(3 * 1024 * 1024);
    const sample = new File([bytes], "vacation-photo.jpg", {
      type: "image/jpeg",
    });
    void filesRef.current.upload(sample);
  }, []);

  const handleChange = async (event: ChangeEvent<HTMLInputElement>) => {
    // Capture the element now — React nulls `currentTarget` after the handler's
    // synchronous phase, so it's gone by the time the uploads below resolve.
    const input = event.currentTarget;
    const picked = input.files;
    if (!picked?.length) {
      return;
    }
    for (const file of picked) {
      // eslint-disable-next-line no-await-in-loop -- demo uploads files one at a time so progress reads cleanly
      await files.upload(`demo/${file.name}`, file, {
        contentType: file.type,
      });
    }
    input.value = "";
  };

  return (
    <div className="flex flex-col gap-4">
      <div>
        <Button
          onClick={() => inputRef.current?.click()}
          type="button"
          variant="outline"
        >
          <UploadIcon />
          Choose files
        </Button>
        <input
          aria-label="Choose files to upload"
          className="hidden"
          multiple
          onChange={(event) => void handleChange(event)}
          ref={inputRef}
          type="file"
        />
      </div>
      <UploadProgress files={files} />
    </div>
  );
};

export default Example;

Installation

npx shadcn@latest add https://files-sdk.dev/r/upload-progress.json

Usage

import { useFiles } from "files-sdk/react";

import { UploadProgress } from "@/components/files-sdk/upload-progress";

export function Uploader() {
  const files = useFiles({ endpoint: "/api/files" });

  return (
    <>
      <button onClick={() => files.upload(/* … */)} type="button">
        Upload
      </button>
      <UploadProgress files={files} />
    </>
  );
}

Each row shows a file’s name, percentage and status (uploading, success, error, aborted). When more than one file is in flight, an aggregate bar is shown on top.

Props

PropType
filesUseFilesResult

A `useFiles()` instance — reads its ambient `uploads` / `progress`.

TypeUseFilesResult
className?string
Typestring

Was this page helpful?