Upload store
Persist multipart state to resume uploads after refresh.
UploadStore stores minimal multipart state (uploadId, key, fileSize) so uploads can resume after refresh or reconnect.
Prefer createLocalStorageStore() for same-browser resume. Use a custom
(e.g. DB-backed) store for cross-device resume or after cleared storage — see
DB hooks.
Use it only for multipart uploads.
Quick setup
import { createLocalStorageStore } from "@dimah-s3/react";
export const localStorageStore = createLocalStorageStore();"use client";
import { UploadButton } from "@dimah-s3/ui";
import { localStorageStore } from "@/lib/upload-store";
export function Uploader() {
return (
<div>
<UploadButton
objectKey={(file) => `uploads/${Date.now()}-${file.name}`}
multipart={true}
uploadStore={localStorageStore}
label="Upload file"
/>
</div>
);
}Built-in stores
createLocalStorageStore()— default browser choice; survives refresh/reopencreateMemoryStore()— in-memory only; useful for SSR-safe setups and tests
import { createMemoryStore } from "@dimah-s3/react";
const uploadStore = createMemoryStore();Custom store
Implement UploadStore when you need your own persistence layer (for example: DB).
Prop
Type
import type { UploadStore } from "@dimah-s3/react";
export const uploadStore: UploadStore = {
async get(key, fileSize) {
// lookup persisted state
return null;
},
async set(upload) {
// persist uploadId state
},
async delete(key) {
// cleanup after complete/abort
},
};Example: DB-backed store
When using @dimah-s3/db multipart hooks, onInit already persists uploadId on the pending row. The client store only needs get — set / delete can be no-ops. Server half: DB hooks.
import type { StoredUpload, UploadStore } from "@dimah-s3/react";
export const uploadStoreDb: UploadStore = {
async get(key, fileSize) {
const res = await fetch(
`/api/upload-store?key=${encodeURIComponent(key)}&fileSize=${fileSize}`,
);
if (!res.ok) return null;
return (await res.json()) as StoredUpload | null;
},
async set() {
// no-op — db plugin multipart.onInit wrote uploadId
},
async delete() {
// no-op — onComplete / onAbort clear the pending row
},
};Use a stable objectKey (not Date.now()):
<UploadButton
objectKey={(file) => `uploads/${userId}/${file.name}`}
multipart
uploadStore={uploadStoreDb}
label="Upload file"
/>Notes
- Resume works when
multipart: trueanduploadStoreare both set. keymust be stable across retries; if it changes every attempt, resume cannot match prior state.- The client still needs the same
Filebytes — the store only recoversuploadId. - For small files or simple uploads, skip
uploadStore.