Declarative UI
Same behaviour as the hook examples, expressed with renderless components.
tsx
import { useMemo } from 'react';
import { useUploader } from 'react-upload-kit';
import { Dropzone, FileList, UploadTrigger } from 'react-upload-kit/components';
import type { UploadAdapter } from 'react-upload-kit';
interface ApiResponse {
url: string;
}
function DeclarativeUploader({ adapter }: { adapter: UploadAdapter<ApiResponse> }) {
const stableAdapter = useMemo(() => adapter, [adapter]);
const uploader = useUploader<ApiResponse>({
adapter: stableAdapter,
accept: ['image/*', '.pdf'],
maxFileSize: 10 * 1024 * 1024,
maxFiles: 8,
autoUpload: true,
concurrency: 2,
maxRetries: 1,
});
return (
<div>
<Dropzone
onDrop={uploader.addFiles}
accept={['image/*', '.pdf']}
maxFileSize={10 * 1024 * 1024}
>
{({ getRootProps, getInputProps, isDragActive, isDragReject }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>
{isDragReject
? 'These files are not allowed'
: isDragActive
? 'Drop the files here'
: 'Drag & drop files here'}
</p>
</div>
)}
</Dropzone>
<UploadTrigger onSelect={uploader.addFiles} accept={['image/*', '.pdf']}>
{({ open, inputProps }) => (
<>
<button type="button" onClick={open}>
Or browse
</button>
<input {...inputProps} />
</>
)}
</UploadTrigger>
{uploader.rejections.map((rejection, index) => (
<p key={`${rejection.file.name}-${index}`}>
{rejection.file.name}: {rejection.errors.map((e) => e.message).join(', ')}
</p>
))}
<FileList
files={uploader.files}
onRemove={uploader.removeFile}
onRetry={uploader.retryFile}
onCancel={uploader.cancelFile}
previewMaxWidth={96}
previewMaxHeight={96}
>
{({ file, preview, isPreviewLoading, remove, retry, cancel }) => (
<div>
{isPreviewLoading && <span>…</span>}
{preview && <img src={preview} alt="" />}
<span>{file.file.name}</span>
<span>{file.status}</span>
<progress value={file.progress} max={100} />
{file.status === 'uploading' && (
<button type="button" onClick={cancel}>
Cancel
</button>
)}
{(file.status === 'error' || file.status === 'cancelled') && (
<button type="button" onClick={retry}>
Retry
</button>
)}
<button type="button" onClick={remove}>
Remove
</button>
</div>
)}
</FileList>
</div>
);
}You still own layout, copy, and CSS. The components only avoid repeating hook wiring. See Components and the component API.