utilcn logoutilcn
Storage

downloadFile

A React component and hook for file downloads with progress tracking

Frontend Implementation

This component is designed for frontend use and handles file downloads through presigned URLs. It pairs with the generate-presigned-download-url function which should be implemented on your backend server to generate secure download URLs.

Usage

Component

import { DownloadFile } from '@/components/download-file';

export function MyPage() {
  return (
    <div>
      <h2>Download your files</h2>
      <DownloadFile fileKey="documents/my-file.pdf" fileName="my-file.pdf" />
    </div>
  );
}

Installation

pnpm dlx shadcn@latest add https://utilcn.dev/r/download-file.json
bunx --bun shadcn@latest add https://utilcn.dev/r/download-file.json
npx shadcn@latest add https://utilcn.dev/r/download-file.json
yarn shadcn@latest add https://utilcn.dev/r/download-file.json

Component API

Parameters

The DownloadFile component accepts the following props:

ParameterTypeDescription
fileKeystringThe storage key/path of the file to download
fileNamestringOptional custom filename for the download
classNamestringOptional CSS classes for styling
childrenReactNodeOptional custom content for the button

Hook API

Parameters

The useDownloadFile hook returns a mutation object that accepts:

ParameterTypeDescription
fileKeystringThe storage key/path of the file to download
fileNamestringOptional custom filename for the download

API Integration

generatePresignedDownloadUrl

Generate presigned URLs for secure file downloads from cloud storage

Dependencies

  • @tanstack/react-query - For mutation management
  • lucide-react - For the download icon

Implementation

DownloadFile Component

'use client';

import { Download } from 'lucide-react';
import type { PropsWithChildren } from 'react';
import { cn } from '@/lib/utils';
import { useDownloadFile } from '@/hooks/use-download-file';

type DownloadFileProps = PropsWithChildren<{
  fileKey: string;
  fileName?: string;
  className?: string;
}>;

export function DownloadFile({
  fileKey,
  fileName,
  children,
  className,
}: DownloadFileProps) {
  const downloadFile = useDownloadFile();

  const handleDownload = () => {
    downloadFile.mutate(
      { fileKey, fileName },
      {
        onSuccess: (url: string) => {
          console.log('Download initiated:', url);
        },
        onError: (err: Error) => {
          console.error(`Download failed: ${(err as Error).message}`);
        },
      },
    );
  };

  return (
    <button
      className={cn('flex items-center gap-2', className)}
      disabled={downloadFile.isPending}
      onClick={handleDownload}
      type="button"
    >
      {children || (
        <>
          <Download className="mr-2 h-4 w-4" />
          {downloadFile.isPending ? 'Downloading...' : 'Download'}
        </>
      )}
    </button>
  );
}

useDownloadFile Hook

import { useMutation } from '@tanstack/react-query';

type DownloadArgs = {
  fileKey: string;
  fileName?: string;
};

export function useDownloadFile() {
  return useMutation({
    mutationFn: async ({ fileKey, fileName }: DownloadArgs) => {
      const downloadRes = await fetch('/api/downloads/presign', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ key: fileKey }),
      });

      if (!downloadRes.ok) throw new Error('Failed to get download URL');
      const { downloadUrl } = await downloadRes.json();

      const response = await fetch(downloadUrl);
      const blob = await response.blob();
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = fileName || fileKey.split('/').pop() || 'download';
      document.body.appendChild(link);
      link.click();
      link.remove();
      window.URL.revokeObjectURL(url);

      return downloadUrl;
    },
  });
}