13. Coming from React
If you have written React, most of what you know transfers — the parts that change are where rendering happens and what a component is allowed to hold.
Capy renders on the server. A unit is a .capy file, a page is a function RENDER, and the output is HTML written with print. There is no virtual DOM, no client bundle, and no file-system router unless you write one.
The mapping
| React | Capy |
|---|---|
| a page component | function RENDER(request : dval) |
| a reusable component | function COMPONENT(request : dval) |
| several components in one file | function COMPONENT:NAME(request : dval) |
props | request.props |
return <div>…</div> | print(…) |
| rendering a child | component_render("card") or component("card") |
A handler returns nothing. Whatever it prints becomes the response, so the React habit of building a tree and returning it becomes writing output in order:
function RENDER(request : dval) {
var name := string(request.query.name, {fallback: "guest"})
print("<main><h1>Hello, ", name, "</h1>")
print("<p>Rendered on the server.</p></main>", "\n")
}
Output
<main><h1>Hello, guest</h1><p>Rendered on the server.</p></main>
Components take explicit props
A React component receives props as an object and can render children. A Capy component receives request.props and has no children or slots — if a fragment needs inner content, pass it as a prop or call another component yourself.
function COMPONENT(request : dval) {
var title := string(request.props.title, {fallback: "Untitled"})
print("<article><h3>", title, "</h3></article>")
}
The caller decides how the result is used. component_render() writes the fragment straight into the response, which is the common case in a layout. component() returns it as a string when you need to wrap, measure, or conditionally emit it.
Props are scoped to the call and restored when it returns, so a component cannot disturb its caller's props and nested components each read their own.
There is no client state
This is the biggest adjustment. There is no useState, no effect hook, and no re-render — a handler runs once per request and produces final HTML.
What replaces them:
per-request shared state —
request.call, readable by every handler in the requestwork that runs once before a unit's handlers —
function ONCE(request : dval)work that runs once when a worker loads a unit —
function INIT(request : dval)state that outlives the request — a session, a file, a database, or a task
A module handle from unit_load() lives only for the current request; do not stash one for later.
Escaping
HTML literals escape interpolated values, like React escapes {value} by default:
function RENDER(request : dval) {
var supplied := string(request.query.q, {fallback: "<script>"})
print(<><p><?= supplied ?></p></>)
}
Use html_escape() when you are building a string by concatenation instead.
What Bearer deliberately does not have
No asset registry, no island registry, no JSX-style component tags, no built-in children or slots. Routing is application code — the starter example checks page files and parent index handlers, which keeps the rules visible rather than implied by directory layout.
When something does not compile
Bearer reports the source path and the compiler output, and writes the full output under BIN_DIRECTORY. A compile error names the unit and line, not a bundler stack.