Skip to content

2. Act on the list

The table lists authors; now a librarian needs to change the list. Every change is an Arc command, and Components turns each one into a form-with-a-button without you binding a single field. We’ll start with RegisterAuthor.

  1. Build the dialog from the command. CommandDialog takes the generated command, renders the fields you list plus the OK/Cancel footer, validates, and executes on confirm. The field accessor (command) => command.name is typed against RegisterAuthor:

    src/AddAuthor.tsx
    import { useState } from 'react';
    import { Guid } from '@cratis/fundamentals';
    import { CommandDialog } from '@cratis/components/CommandDialog';
    import { InputTextField } from '@cratis/components/CommandForm';
    import { RegisterAuthor } from './Authors/RegisterAuthor'; // generated proxy
    export const AddAuthor = () => {
    const [id] = useState(() => Guid.create());
    return (
    <CommandDialog<RegisterAuthor>
    command={RegisterAuthor}
    title='Add author'
    okLabel='Add'
    initialValues={{ id }}
    >
    <InputTextField<RegisterAuthor>
    value={(command) => command.name}
    title='Name'
    placeholder='Sample Author'
    />
    </CommandDialog>
    );
    };

    RegisterAuthor requires an id that the librarian never types, so the dialog creates one and passes it in initialValues. The command is complete except for the name from the moment the dialog opens.

  2. Open it from the screen. useDialog from Arc gives you a wrapper element to render and a function to show it. Add a button above the table from chapter 1:

    src/Authors.tsx
    import { useDialog } from '@cratis/arc.react/dialogs';
    import { Button, Page } from '@cratis/components/Common';
    import { Column, DataTableForObservableQuery } from '@cratis/components/DataTables';
    import { AllAuthors } from './Authors/Author';
    import { AddAuthor } from './AddAuthor';
    export const Authors = () => {
    const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor);
    return (
    <Page title='Authors'>
    <Button label='Add author' onClick={() => void showAddAuthor()} />
    <DataTableForObservableQuery query={AllAuthors} emptyMessage='No authors yet'>
    <Column field='name' header='Name' sortable />
    </DataTableForObservableQuery>
    <AddAuthorDialog />
    </Page>
    );
    };
  3. Try it. Click Add author. The dialog opens with Add disabled, because the required name is still empty. Type a name and Add enables. Confirm, and the dialog shows its busy state while RegisterAuthor executes, then closes.

useDialog mounts AddAuthor only while the dialog is open, so every click creates a fresh id. You didn’t wire the buttons either: CommandDialog closes itself through the dialog context when the command succeeds, and Cancel, the close button, and Escape close it without executing.

Add an author and the new row appears in the table immediately — and you wrote nothing to make that happen. There’s no “reload the list after saving,” because there’s nothing to reload: the command stored the author, the observable AllAuthors query pushed its new result, and the table you built in chapter 1 re-rendered on its own. The write side and the read side meet in the data, and the screen just follows.

Editing isn’t a different mechanism — it’s another command. The Arc tutorial doesn’t define rename or remove commands, but if you add a RenameAuthor command with an id and a name, its dialog follows the same shape. It needs to know which author, so pass the selected author’s id and current name as initialValues. This excerpt assumes an author variable holding the selected row:

Excerpt: a RenameAuthor dialog
<CommandDialog<RenameAuthor>
command={RenameAuthor}
title='Rename author'
okLabel='Save'
initialValues={{ id: author.id, name: author.name }}
>
<InputTextField<RenameAuthor> value={(command) => command.name} title='Name' />
</CommandDialog>

A remove command with nothing but the id is often a confirmation rather than a form; pass initialFocus={DialogInitialFocus.Cancel} (from @cratis/components/Dialogs) so a stray Enter doesn’t confirm it. The table updates itself after every one of these, the same way it did for adding. In the next chapter you’ll get the selected row from DataPage and pass it to a real command from the Arc tutorial, AddBook.

  • An Add author dialog generated from the RegisterAuthor command, opened with useDialog, with its required id in initialValues,
  • a table that updates live the moment the command lands — no refresh code,
  • and the pattern for every other change: model it as a command, open it in a dialog, let the list follow.

These actions hang off a button above the table, but a real list screen lets you select a row and act on it — and often shows that row’s details beside the list. That’s exactly what DataPage is for. Let’s build the list-and-detail screen →