React upload hooks for Beignet upload clients.
bun add @beignet/react-uploads react
Create a typed upload client with @beignet/core/uploads/client, then wrap it
once for React components:
// client/uploads.ts
import { createUploadClient } from "@beignet/core/uploads/client";
import { createReactUploads } from "@beignet/react-uploads";
import type { postUploads } from "@/features/posts/uploads";
export type AppUploads = typeof postUploads;
export const uploads = createUploadClient<AppUploads>({
baseUrl: "/api/uploads",
});
export const reactUploads = createReactUploads({
uploads,
});
Use the hook in components:
"use client";
import { reactUploads } from "@/client/uploads";
export function AttachmentPicker({ postSlug }: { postSlug: string }) {
const attachment = reactUploads.useUpload("posts.attachment");
return (
<input
type="file"
accept={attachment.accept}
disabled={attachment.isUploading}
onChange={(event) => {
const files = Array.from(event.currentTarget.files ?? []);
if (files.length === 0) return;
attachment.upload({
metadata: { postSlug },
files,
});
}}
/>
);
}
The hook exposes status, progress, error, result, reset, and abort.
It keeps uploads imperative and does not hide React Query; invalidate queries in
onSuccess when an upload changes visible app state.
const attachment = reactUploads.useUpload("posts.attachment", {
onSuccess() {
queryClient.invalidateQueries({ queryKey: rq(getPost).key() });
},
});
createReactUploads({ uploads }) creates an adapter bound to a typed upload
client.adapter.useUpload(name, options?) returns state and methods for one upload
workflow.adapter.useUploadMany(name, options?) returns the same state with an
upload(files, options) convenience method.@beignet/core/uploads - upload definitions
and runtime router@beignet/core/uploads/client - typed browser
upload client