React Integration
Torrin integrates seamlessly with React applications. This guide shows you how to build a complete upload component with progress tracking, pause/resume, and error handling.
Installation
bash
npm install @torrin-kit/clientbash
yarn add @torrin-kit/clientbash
pnpm add @torrin-kit/clientbash
bun add @torrin-kit/clientBasic Example
A simple file uploader with progress tracking:
tsx
import { useState } from 'react';
import { createTorrinClient } from '@torrin-kit/client';
const torrin = createTorrinClient({
endpoint: 'http://localhost:3000/api/uploads',
});
export default function FileUploader() {
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState('idle');
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const upload = torrin.createUpload({ file });
upload.on('progress', (p) => {
setProgress(p.percentage);
});
upload.on('status', (s) => {
setStatus(s);
});
try {
const result = await upload.start();
console.log('Upload complete:', result.location);
} catch (error) {
console.error('Upload failed:', error);
}
};
return (
<div className="space-y-4">
<input
type="file"
onChange={handleFileChange}
className="block w-full text-sm"
/>
{status !== 'idle' && (
<div className="space-y-2">
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-blue-600 h-2.5 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<div className="flex justify-between text-sm">
<span>{status}</span>
<span>{progress.toFixed(1)}%</span>
</div>
</div>
)}
</div>
);
}Advanced Example
A complete uploader with pause, resume, and cancel functionality:
tsx
import { useState, useRef } from 'react';
import { createTorrinClient, type TorrinUpload } from '@torrin-kit/client';
const torrin = createTorrinClient({
endpoint: 'http://localhost:3000/api/uploads',
});
export default function AdvancedUploader() {
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState<string>('idle');
const [error, setError] = useState<string | null>(null);
const uploadRef = useRef<TorrinUpload | null>(null);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setError(null);
setProgress(0);
const upload = torrin.createUpload({
file,
metadata: {
userId: 'user123',
timestamp: new Date().toISOString()
}
});
uploadRef.current = upload;
upload.on('progress', (p) => {
setProgress(p.percentage);
console.log(`${p.chunksCompleted}/${p.totalChunks} chunks uploaded`);
});
upload.on('status', (s) => {
setStatus(s);
});
upload.on('error', (err) => {
setError(err.message);
});
try {
const result = await upload.start();
console.log('Upload complete:', result);
uploadRef.current = null;
} catch (err: any) {
setError(err.message);
}
};
const handlePause = () => {
uploadRef.current?.pause();
};
const handleResume = () => {
uploadRef.current?.resume();
};
const handleCancel = async () => {
await uploadRef.current?.cancel();
uploadRef.current = null;
setProgress(0);
setStatus('idle');
};
return (
<div className="max-w-md p-6 bg-white rounded-lg shadow">
<h2 className="text-xl font-bold mb-4">Upload File</h2>
<input
type="file"
onChange={handleFileChange}
disabled={status === 'uploading'}
className="block w-full mb-4"
/>
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded">
<p className="text-sm text-red-600">{error}</p>
</div>
)}
{status !== 'idle' && (
<div className="space-y-4">
<div>
<div className="flex justify-between text-sm mb-1">
<span className="font-medium">Status: {status}</span>
<span>{progress.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className="bg-blue-600 h-3 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
</div>
<div className="flex gap-2">
{status === 'uploading' && (
<button
onClick={handlePause}
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
>
Pause
</button>
)}
{status === 'paused' && (
<button
onClick={handleResume}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
>
Resume
</button>
)}
{(status === 'uploading' || status === 'paused') && (
<button
onClick={handleCancel}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Cancel
</button>
)}
</div>
</div>
)}
</div>
);
}Custom Hook
Create a reusable hook for upload management:
typescript
import { useState, useCallback, useRef } from 'react';
import { createTorrinClient, type TorrinUpload } from '@torrin-kit/client';
const torrin = createTorrinClient({
endpoint: 'http://localhost:3000/api/uploads',
});
export function useTorrinUpload() {
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState<string>('idle');
const [error, setError] = useState<string | null>(null);
const uploadRef = useRef<TorrinUpload | null>(null);
const startUpload = useCallback(async (file: File, metadata?: Record<string, any>) => {
setError(null);
setProgress(0);
const upload = torrin.createUpload({ file, metadata });
uploadRef.current = upload;
upload.on('progress', (p) => setProgress(p.percentage));
upload.on('status', (s) => setStatus(s));
upload.on('error', (err) => setError(err.message));
try {
return await upload.start();
} catch (err: any) {
setError(err.message);
throw err;
}
}, []);
const pause = useCallback(() => {
uploadRef.current?.pause();
}, []);
const resume = useCallback(() => {
uploadRef.current?.resume();
}, []);
const cancel = useCallback(async () => {
await uploadRef.current?.cancel();
uploadRef.current = null;
setProgress(0);
setStatus('idle');
}, []);
return {
progress,
status,
error,
startUpload,
pause,
resume,
cancel,
};
}Usage:
tsx
function MyUploader() {
const { progress, status, error, startUpload, pause, resume, cancel } = useTorrinUpload();
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
try {
const result = await startUpload(file, { userId: '123' });
console.log('Done:', result);
} catch (err) {
console.error('Failed:', err);
}
}
};
return (
<div>
<input type="file" onChange={handleFile} />
<p>Progress: {progress}%</p>
<p>Status: {status}</p>
{error && <p>Error: {error}</p>}
<button onClick={pause}>Pause</button>
<button onClick={resume}>Resume</button>
<button onClick={cancel}>Cancel</button>
</div>
);
}With Resume Support
Enable automatic resume after page refresh:
typescript
import { createTorrinClient, createLocalStorageResumeStore } from '@torrin-kit/client';
const torrin = createTorrinClient({
endpoint: 'http://localhost:3000/api/uploads',
resumeStore: createLocalStorageResumeStore(), // Enable auto-resume
});Now if the user refreshes the page and selects the same file, the upload will automatically resume from where it left off.
Next Steps
- Vue Integration - Vue.js examples
- Svelte Integration - Svelte examples
- Configuration - Customize chunk size, concurrency, etc.
- Error Handling - Handle errors gracefully