Skip to content

Recipe: A list screen with actions

Goal: the most common screen in any app — a table of things with a toolbar to add, and per-row actions to edit or remove. This recipe wires displaying data and running commands together.

  • A live table reads an observable query, so it reflects changes the instant a command lands.
  • A toolbar button opens an “add” CommandDialog.
  • Row actions open edit/remove command dialogs for the selected item.
import { DataPage, MenuItem } from '@cratis/components/DataPage';
import { Column } from 'primereact/column';
import { useDialog } from '@cratis/arc.react/dialogs';
import { AllAuthors } from './Author'; // observable query proxy
import { AddAuthor } from './AddAuthor'; // CommandDialog from the form recipe
export const Authors = () => {
const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor);
return (
<>
<DataPage
title="Authors"
query={AllAuthors}
emptyMessage="No authors yet">
<DataPage.MenuItems>
<MenuItem label="Add author" icon="pi pi-plus" command={() => showAddAuthor()} />
</DataPage.MenuItems>
<DataPage.Columns>
<Column field="name" header="Name" sortable />
</DataPage.Columns>
</DataPage>
<AddAuthorDialog />
</>
);
};

You never write the glue between the write and the read: the “add” command appends an event, the projection updates the read model, and the observable table re-renders — automatically. There’s no “refresh the list after saving” code because there’s nothing to refresh.

  • Edit and remove are just more commands. Model them as commands (RenameAuthor, RemoveAuthor) and open them in dialogs the same way; the table updates itself.
  • Keep the table’s read model specialized. The list query and a detail query can read different, purpose-built read models — don’t force one model to serve every screen.
  • For a plain table without the detail panel, use DataTableForObservableQuery directly.