Skip to content

1. List the authors

A library screen starts with a list. We have an AllAuthors query from the backend, generated into a typed proxy — so the first job is to get its results onto the screen in a table. And because that query is observable, the table will stay current on its own.

  1. Render the table. DataTableForObservableQuery takes the query proxy, subscribes to it, and renders each result as a row. You describe the columns with the Cratis-owned Column marker. Its field string must match a property on the generated Author read model:

    src/Authors.tsx
    import { Page } from '@cratis/components/Common';
    import { Column, DataTableForObservableQuery } from '@cratis/components/DataTables';
    import { AllAuthors } from './Authors/Author'; // generated observable query proxy
    export const Authors = () => (
    <Page title='Authors'>
    <DataTableForObservableQuery query={AllAuthors} emptyMessage='No authors yet'>
    <Column field='name' header='Name' sortable />
    </DataTableForObservableQuery>
    </Page>
    );
  2. Put it on screen. Render <Authors /> inside the providers from Getting started — in the Arc tutorial’s src/App.tsx, that means replacing its plain list with this component. Run the frontend and open the page. You see a table with a Name column: Loading… until the first result arrives, then one row per author you registered in the Arc tutorial, or No authors yet if there are none. A failed query shows Could not load data. instead of the empty message; an unauthorized query shows You are not authorized to view this data.

That’s the whole screen. Page is the layout root for a routable view: a column that fills its parent. Its title is rendered, as a heading, only when you add showTitle; it doesn’t set the browser tab title. The table does the rest.

Notice everything that’s absent. There’s no fetch, no useEffect, no loading flag, no useState holding the rows, and — the part that matters most — no subscription code. DataTableForObservableQuery opened the observable for you. The moment something else in the app registers a new author, the backend pushes the new query result down, and this table re-renders with the extra row. You’ll see that happen for real in the next chapter, when we add the form.

The generated query and its row values are typed, but Column.field is currently a plain string and the compiler does not validate it against the read model. Keep field names aligned with the generated row type and update them when the backend property changes. For custom cells, use Column<Author> with a typed body callback so row-property access is compiler-checked. A misspelled raw field value is a runtime display/configuration error rather than a TypeScript error.

  • A library screen that lists every author in a table,
  • bound to the AllAuthors query with no subscription or fetch code,
  • that will update itself the instant the data behind it changes.

A read-only list isn’t a back office, though — a librarian needs to add authors. Next we’ll give the screen a command. Let’s act on the list →