Storage
uploadFile
A React component and hook for file uploads with progress tracking
Frontend Implementation
This component is designed for frontend use and handles file uploads through presigned URLs. It pairs with the generate-presigned-upload-url function which should be implemented on your backend server to generate secure upload URLs.
Usage
Component
import { UploadFile } from '@/lib/upload-file';
export function MyPage() {
return (
<div>
<h2>Upload your files</h2>
<UploadFile />
</div>
);
}Hook
import { useUploadFile } from '@/hooks/use-upload-file';
export function CustomUploadComponent() {
const { uploadFile } = useUploadFile();
const handleUpload = (file: File) => {
uploadFile({
file,
onSuccess: (url) => console.log('Uploaded to:', url),
onError: (err) => console.error('Upload failed:', err),
onProgress: (percent) => console.log(`Progress: ${percent}%`),
});
};
return (
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleUpload(file);
}}
/>
);
}Installation
pnpm dlx shadcn@latest add https://utilcn.dev/r/upload-file.jsonbunx --bun shadcn@latest add https://utilcn.dev/r/upload-file.jsonnpx shadcn@latest add https://utilcn.dev/r/upload-file.jsonyarn shadcn@latest add https://utilcn.dev/r/upload-file.jsonHook API
Parameters
The useUploadFile hook returns an object with an uploadFile function that accepts:
| Parameter | Type | Description |
|---|---|---|
file | File | The file to upload |
onProgress | (percent: number) => void | Optional progress callback |
onSuccess | (fileUrl: string) => void | Optional success callback with file URL |
onError | (error: Error) => void | Optional error callback |
API Integration
generatePresignedUploadUrl
Generate presigned URLs for secure file uploads to cloud storage
Implementation
UploadFile Component
'use client';
import { type ChangeEvent, useState } from 'react';
import { useUploadFile } from '@/hooks/use-upload-file';
export function UploadFile() {
const [progress, setProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);
const { uploadFile } = useUploadFile();
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
return;
}
setIsUploading(true);
uploadFile({
file,
onProgress: (p: number) => setProgress(p),
onSuccess: (url: string) => {
console.log('Uploaded to:', url);
setIsUploading(false);
setProgress(0);
},
onError: (err: Error) => {
console.error(`Upload failed: ${err.message}`);
setIsUploading(false);
setProgress(0);
},
});
};
return (
<div className="flex flex-col gap-4">
<input
className="file:mr-4 file:rounded-md file:border-0 file:bg-secondary file:px-4 file:py-2 file:font-semibold file:text-secondary-foreground file:text-sm hover:file:bg-secondary/80"
onChange={handleFileChange}
type="file"
/>
{isUploading && (
<div className="mt-4 flex items-center gap-2">
<div className="h-2 w-full rounded-full bg-secondary">
<div
className="h-2 rounded-full bg-primary transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-muted-foreground text-sm">{progress}%</span>
</div>
)}
</div>
);
}useUploadFile Hook
import { useCallback } from 'react';
type UploadArgs = {
file: File;
onProgress?: (percent: number) => void;
onSuccess?: (fileUrl: string) => void;
onError?: (error: Error) => void;
};
export function useUploadFile() {
const uploadFile = useCallback(
async ({ file, onProgress, onSuccess, onError }: UploadArgs) => {
try {
const presignRes = await fetch('http://localhost:8080/uploadFile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
contentLength: file.size,
}),
});
if (!presignRes.ok) {
throw new Error('Failed to get presigned URL');
}
const presign = await presignRes.json();
await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', presign.uploadUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.upload.onprogress = (evt) => {
if (evt.lengthComputable && onProgress) {
const PERCENTAGE_MULTIPLIER = 100;
const percent = Math.round(
(evt.loaded * PERCENTAGE_MULTIPLIER) / evt.total,
);
onProgress(percent);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
} else {
reject(new Error('Upload failed'));
}
};
xhr.onerror = () => reject(new Error('Upload failed'));
xhr.send(file);
});
const fileUrl = presign.fileUrl as string;
onSuccess?.(fileUrl);
return fileUrl;
} catch (error) {
const uploadError =
error instanceof Error ? error : new Error('Upload failed');
onError?.(uploadError);
throw uploadError;
}
},
[],
);
return { uploadFile };
}