OpenUI

List dashboards

Show the user's saved dashboards with useArtifactList, and pin, unpin, rename, delete, and open them.

useArtifactList() returns the signed-in user's saved dashboards, newest first, page by page. Separate hooks pin, unpin, rename, and delete them. Every list and renderer in the app shares one cache, so a change shows everywhere at once.

components/dashboard-list.tsx
"use client";

import { useArtifactList } from "@openuidev/react-ui/cloud";

export function DashboardList({ onOpen }: { onOpen: (id: string) => void }) {
  const { artifacts, isLoading, error, hasMore, loadMore } = useArtifactList({
    filter: { type: "dashboard" },
  });

  if (error) return <p>Couldn't load dashboards.</p>;
  if (!isLoading && artifacts.length === 0) return <p>No dashboards yet.</p>;

  return (
    <ul>
      {artifacts.map((dashboard) => (
        <li key={dashboard.id}>
          <button onClick={() => onOpen(dashboard.id)}>{dashboard.title || "Untitled"}</button>
        </li>
      ))}
      {hasMore && <button onClick={loadMore}>Show more</button>}
    </ul>
  );
}

The hook must sit inside OpenUICloudProvider.

Filter

FieldDescription
typeOnly these kinds, such as "dashboard".
pinnedtrue for pinned dashboards only, false for unpinned.
searchMatch on the title.
limitPage size.

Returns

FieldDescription
artifactsThe dashboards loaded so far: id, title, type, updatedAt, pinned.
isLoading, errorThe load state.
hasMore, loadMoreLoad the next page.
refreshLoad the list again from the first page.

Pin, rename, and delete

Each action has its own hook:

HookReturns
useArtifactPin()pin(id)
useArtifactUnpin()unpin(id)
useArtifactUpdate()update(id, { title }), which renames the dashboard
useArtifactDelete()deleteArtifact(id)

Each also returns isPending and error for its last call. The actions update every list and renderer right away, and revert if the request fails. A deleted dashboard can't be restored.

import { useArtifactDelete, useArtifactPin, useArtifactUpdate } from "@openuidev/react-ui/cloud";

function DashboardActions({ id, title }: { id: string; title: string }) {
  const { pin } = useArtifactPin();
  const { update } = useArtifactUpdate();
  const { deleteArtifact, isPending } = useArtifactDelete();

  return (
    <>
      <button onClick={() => pin(id)}>Pin</button>
      <button onClick={() => update(id, { title: `${title} (copy)` })}>Rename</button>
      <button disabled={isPending} onClick={() => deleteArtifact(id)}>
        Delete
      </button>
    </>
  );
}

The hooks must sit inside OpenUICloudProvider, like the list.

Open a dashboard

Pass the id to LiveArtifactRenderer:

<LiveArtifactRenderer
  id={id}
  library={library}
  interpreter={{ url: "/api/interpreter" }}
  preview={false}
/>

A renderer opened by id shows the latest version, and switches when an edit finishes, like one opened from a tool response.

Pinned and recent

Use two lists to show pinned dashboards above the rest:

const pinned = useArtifactList({ filter: { type: "dashboard", pinned: true } });
const recent = useArtifactList({ filter: { type: "dashboard", pinned: false, limit: 20 } });
const { pin } = useArtifactPin();
const { unpin } = useArtifactUnpin();

async function setPinned(id: string, next: boolean) {
  await (next ? pin(id) : unpin(id));
  pinned.refresh();
  recent.refresh();
}

Pinning moves a dashboard from one list to the other, so refresh both.

New dashboards

A new dashboard joins the list when it finishes generating and is saved. To show it right away, call refresh when a renderer stops streaming:

function RefreshWhenSaved({ onSaved }: { onSaved: () => void }) {
  const { isStreaming } = useArtifactRenderer();
  const wasStreaming = useRef(isStreaming);
  useEffect(() => {
    if (wasStreaming.current && !isStreaming) onSaved();
    wasStreaming.current = isStreaming;
  }, [isStreaming, onSaved]);
  return null;
}

Render it in the children of a renderer slot, such as a custom Chrome, and pass the list's refresh as onSaved.

On this page