dimah-s3v0.4.1

Setup

Minimal client setup with createS3Client and one upload button.

Install

npm i @dimah-s3/react

API client

Hooks use an S3Api client. Prefer createS3Client — one call gives you the client, a bound provider, and a typed useApi.

If your backend uses @dimah-s3/server on /api/s3:

components/s3-provider.tsx
"use client";

import { createS3Client } from "@dimah-s3/react";

export const { api, S3Provider, useApi } = createS3Client();

export function S3ClientProvider({ children }: { children: React.ReactNode }) {
  return <S3Provider>{children}</S3Provider>;
}

Custom base path / plugins / auth headers:

import { createS3Client } from "@dimah-s3/react";
import { dbClient } from "@dimah-s3/db/client";

export const { api, S3Provider, useApi } = createS3Client({
  basePath: "/api/your-path",
  plugins: [dbClient()],
  // cookies (same-origin) or Authorization via headers factory
  credentials: "include",
  headers: async () => ({
    Authorization: `Bearer ${await getAccessToken()}`,
  }),
});

Failed API calls throw DimahS3Error from @dimah-s3/core (with .status). Upload engine failures throw S3UploadError (extends it — .code, .status, .phase).

Use api directly (like Better Auth's client) or useApi() inside React.

Provider

Mount the provider once near your app root.

app/layout.tsx
import { S3ClientProvider } from "@/components/s3-provider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <S3ClientProvider>{children}</S3ClientProvider>
      </body>
    </html>
  );
}

Using @dimah-s3/ui? Also mount <Toaster /> from @dimah-s3/ui (or @/components/ui/toast for registry) next to the provider — see UI setup.

Now hooks inside this provider can omit api.

First hook

app/uploader.tsx
"use client";

import { useUploadControls } from "@dimah-s3/react";

export function Uploader() {
  const { openFilePicker, inputProps, progress, isUploading } =
    useUploadControls({
      objectKey: (file) => `uploads/${Date.now()}-${file.name}`,
      accept: ["image/*", ".pdf"],
      maxFileSize: 10 * 1024 * 1024,
      onSuccess: (_file, result) => console.log(result.key),
    });

  return (
    <div>
      <input {...inputProps} />
      <button type="button" onClick={openFilePicker} disabled={isUploading}>
        {isUploading ? `Uploading ${progress.percent}%` : "Upload file"}
      </button>
    </div>
  );
}

If your first screen only needs upload, keep other operations off:

lib/s3.ts
import { dimahS3 } from "@dimah-s3/server";
import { s3Client, defaultBucket } from "@/lib/s3-client";

export const s3 = dimahS3({
  s3: s3Client,
  defaultBucket,
  upload: { enabled: true },
  download: { enabled: false },
  delete: { enabled: false },
  multipart: { enabled: false },
});

Enable other features later when you need them.

Resumable uploads (upload store)

For multipart resume across refreshes, add an upload store:

lib/upload-store.ts
import { createLocalStorageStore } from "@dimah-s3/react";

export const localStorageStore = createLocalStorageStore();
app/uploader.tsx
import { useUploadControls } from "@dimah-s3/react";
import { localStorageStore } from "@/lib/upload-store";

const { openFilePicker, inputProps } = useUploadControls({
  objectKey: (file) => `uploads/${Date.now()}-${file.name}`,
  multipart: true,
  uploadStore: localStorageStore,
});

Full guide: Upload store.

Next

// Minimal upload-ready UI:
// mount provider once, render one button, done.

Hook reference: Upload, Download, Delete. UI components: UI.

On this page