Getting started (React)
You finished the backend slice: a RegisterAuthor command and an AllAuthors query, and dotnet build turned both into typed TypeScript proxies. Now for the other half — a React screen that lists authors and adds one.
Normally this is where the type safety ends. You’d hand-write a fetch wrapper, redeclare the request and response shapes in TypeScript, and hope the two sides stay in sync. Arc skips all of that: the proxies are generated from your C#, so the frontend already knows the exact types — and the compiler catches you the moment they drift. Let’s read data and run a command through them.
Prerequisites
Section titled “Prerequisites”- The backend slice from Your first command and query — its
RegisterAuthorcommand andAllAuthorsquery are what this screen calls. - A successful
dotnet build— that’s what generates the TypeScript proxies the frontend imports. - A Vite + React app with
@cratis/arc.reactand@cratis/componentsinstalled — thedotnet new cratistemplate scaffolds exactly that.
Wire it up
Section titled “Wire it up”-
Initialize the bindings and mount the providers. The
cratistemplate gives you a Vite + React app with@cratis/arc.reactand@cratis/componentsinstalled. Two things happen at startup — the generated bindings learn how to reach the backend, and the app is wrapped in the Cratis providers:App.tsx import { Bindings } from './Bindings'; // generatedimport { CratisComponentsProvider } from '@cratis/components';Bindings.initialize();export const App = () => (<CratisComponentsProvider><YourRoutes /></CratisComponentsProvider>); -
Read data with a query. The query you wrote in C# is generated as a typed proxy. Because
AllAuthorsis an observable query, the.use()hook re-renders the component whenever the underlying read model changes — live updates, no polling:Authors.tsx import { AllAuthors } from './Authors/Author'; // generated proxyexport const Authors = () => {const [authors] = AllAuthors.use();return (<ul>{authors.data.map(a => <li key={String(a.id)}>{a.name}</li>)}</ul>);}; -
Execute a command.
CommandDialogruns a generated command — it instantiates it, renders the form fields and the confirm/cancel buttons, and disables confirm while it executes:AddAuthor.tsx import { CommandDialog } from '@cratis/components/CommandDialog';import { InputTextField } from '@cratis/components/CommandForm';import { RegisterAuthor } from './Authors/RegisterAuthor'; // generated proxyexport const AddAuthor = () => (<CommandDialog<RegisterAuthor> command={RegisterAuthor} title="Add author" okLabel="Add"><InputTextField<RegisterAuthor> value={i => i.name} title="Name" /></CommandDialog>);
Where the type safety pays off
Section titled “Where the type safety pays off”Look at the field accessor i => i.name. It isn’t a string you typed and hope matches — it’s a property on the generated RegisterAuthor type. Rename Name in the C# command, rebuild, and i => i.name stops compiling until you update it. The same goes for a.name on the query side. There’s no second source of truth to drift, and no runtime surprise when a shape changes: the mismatch is a build error, on your machine, before anyone runs it.
And the form is wired to the live list for nothing extra — AllAuthors.use() is subscribed to the read model, so the moment the command writes, the list re-renders. You didn’t write any of that plumbing.
You initialized the generated bindings, read a read model through a typed, observable query proxy, and executed a typed command with CommandDialog — and the whole screen stays type-checked against the C# it came from. That round trip, C# to React and back, all typed, is the thing Arc exists to give you.
Where to go next
Section titled “Where to go next”- Build a full-stack feature — the same slice, end to end, backend and frontend together.
- Go deeper on the React integration, or the MVVM with React approach for larger screens.
- Browse the building blocks in Components.