DreamDB

React example

A complete component that opens a Space, paginates its samples, and renders thumbnails via blobUrlFor. ~100 lines, no extra deps beyond React.

Setup

bash
pnpm add @dreamlake/dreamdb react react-dom

The component

tsx
import { useEffect, useMemo, useState } from "react";
import { Space } from "@dreamlake/dreamdb";
import { blobUrlFor, revokeBlobUrl } from "@dreamlake/dreamdb/browser";
import type { Sample } from "@dreamlake/dreamdb";

const SPACE_URI =
  "https://vortex-test-20260518.s3.us-east-1.amazonaws.com/refs/imagenet-10k-clip";

export default function ImageGrid() {
  const [space, setSpace] = useState<Space | null>(null);
  const [samples, setSamples] = useState<Sample[]>([]);
  const [page, setPage] = useState(0);
  const pageSize = 48;

  useEffect(() => {
    Space.fromUri(SPACE_URI).then(async (sp) => {
      setSpace(sp);
      const { samples } = await sp.samples();
      setSamples(samples);
    });
  }, []);

  const visible = useMemo(
    () => samples.slice(page * pageSize, (page + 1) * pageSize),
    [samples, page],
  );

  if (!space) return <p>Opening Space…</p>;
  return (
    <div>
      <header className="mb-2 flex items-center gap-3 text-sm">
        <span>{samples.length} samples</span>
        <button
          onClick={() => setPage((p) => Math.max(0, p - 1))}
          disabled={page === 0}
        >
          prev
        </button>
        <span>page {page + 1}</span>
        <button
          onClick={() => setPage((p) => p + 1)}
          disabled={(page + 1) * pageSize >= samples.length}
        >
          next
        </button>
      </header>
      <div className="grid grid-cols-6 gap-1">
        {visible.map((s) => (
          <Thumb key={String(s.anchor)} sample={s} />
        ))}
      </div>
    </div>
  );
}

function Thumb({ sample }: { sample: Sample }) {
  const [url, setUrl] = useState<string | null>(null);

  useEffect(() => {
    let revoked = false;
    let resolved: string | null = null;
    blobUrlFor(sample, "image").then((u) => {
      if (revoked || !u) return;
      resolved = u;
      setUrl(u);
    });
    return () => {
      revoked = true;
      if (resolved) revokeBlobUrl(resolved);
    };
  }, [sample]);

  return (
    <div className="aspect-square bg-neutral-100">
      {url && (
        <img
          src={url}
          alt={`anchor ${sample.anchor}`}
          className="h-full w-full object-cover"
        />
      )}
    </div>
  );
}

Things to notice

  • One Space per page. Space.fromUri fetches the ref + the Manifest; cache that in state, don't re-open per render.
  • Pagination is client-side. samples() returns the whole materialized list — the SDK's eager fan-out. For >100K samples switch to Dataset.iterStream server-side or space.itemsInRange for windowed reads.
  • Each thumb has its own useEffect. The cleanup function revokes the URL when the sample changes or the component unmounts. Without that you leak bytes.
  • revoked flag. Handles the late-arriving fetch (resolved after unmount) so you don't try to revoke a URL that was never published.

Going further

The full demo app at dreamdb-workspace/demo/ shows:

  • IntersectionObserver-based lazy loading
  • React Query for content-addressed caching (staleTime: Infinity on Manifest hashes; finite on mutable refs)
  • Zustand for the bucket-list state with localStorage persistence
  • Tailwind v4 styling
  • A semantic-search bar (see Semantic search)