Skip to content

3. List and detail

Our screen lists authors and acts on them, but the actions hang off a lone button, and there’s nowhere to see an author — their books, in particular. The everyday shape for this is a table on the left, details on the right, with a toolbar that knows whether a row is selected. That layout has enough moving pieces to deserve a component; DataPage hands it to you whole.

We’ll point it at AllAuthorsWithBooks — the read model from the Arc tutorial where each author already carries their books — so the detail panel has something to show.

  1. Describe the detail panel. It’s an ordinary component that receives the selected item. Because each AuthorWithBooks carries its books, listing them is a map — no second query:

    AuthorDetails.tsx
    export const AuthorDetails = ({ item }) => (
    <div className="p-4">
    <h2>{item.name}</h2>
    <h3>Books</h3>
    <ul>
    {item.books.map(book => <li key={String(book.id)}>{book.title}</li>)}
    </ul>
    </div>
    );
  2. Assemble the page. DataPage takes the query, the columns, the menu actions, and the detail component. Selection, the resizable split, and hiding the panel when nothing is selected all come built in:

    Authors.tsx
    import { DataPage, MenuItem } from '@cratis/components/DataPage';
    import { Column } from 'primereact/column';
    import { useDialog } from '@cratis/arc.react/dialogs';
    import { AllAuthorsWithBooks } from './Authors/AuthorWithBooks'; // generated proxy
    import { AddAuthor } from './AddAuthor';
    import { AuthorDetails } from './AuthorDetails';
    export const Authors = () => {
    const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor);
    return (
    <>
    <DataPage
    title="Authors"
    query={AllAuthorsWithBooks}
    emptyMessage="No authors yet"
    detailsComponent={AuthorDetails}>
    <DataPage.MenuItems>
    <MenuItem label="Add author" icon="pi pi-plus" command={() => showAddAuthor()} />
    <MenuItem label="Edit" icon="pi pi-pencil" disableOnUnselected command={() => {/* open RenameAuthor */}} />
    </DataPage.MenuItems>
    <DataPage.Columns>
    <Column field="name" header="Name" sortable />
    </DataPage.Columns>
    </DataPage>
    <AddAuthorDialog />
    </>
    );
    };

Select an author and their books appear on the right. Drag the divider to resize. The Edit item is disabled until a row is selected — disableOnUnselected wires that to selection for you.

Three chapters ago we had a generated query and no screen. Now we have a real library back office:

  • Chapter 1 — a live table from AllAuthors, bound to an observable query with no fetch or subscription code.
  • Chapter 2 — add, rename, and remove through CommandDialog + useDialog, with the table updating itself the instant a command lands.
  • Chapter 3 — a DataPage list-and-detail layout: selection, menu actions, and a resizable panel showing each author’s books.

The pattern under all of it is the same one Components exists to give you: point a component at a generated proxy, and the wiring is already done. A query becomes a live table; a command becomes a validated form; a read model becomes a detail panel — and every one of them stays type-checked against the C# it came from.

You’ve built a screen, and you have the pattern for every other one. That’s Components doing its job — go build the rest of your app.