14. 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() |
| a reusable component | function COMPONENT(props : any) |
| several components in one file | function COMPONENT:NAME(props : any) |
props | props |
return <div>…</div> | print(…) |
| rendering a child | component_render("card") or component("card") |
try { … } catch (e) { … } | try value := operation() { … } else { … } for a function that returns a result |
a ?? b | a ?? b, where a is an any and the default applies only when a is none |
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()
{
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 a props parameter and has no children or slots. Pass inner content as a prop or call another component.
function COMPONENT(props : any)
{
var title := string(props.title, fallback: "Untitled")
print("<article><h3>", title, "</h3></article>")
}
Output
<article><h3>Untitled</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.
Each component call passes its own props argument. Props are shared objects, so changes to their contents remain visible to the caller.
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 uses application keys such as
request.user, readable by every handler in the request.function ONCE()emits shared CSS or JavaScript before the unit's first render or component invocation in each request.function INIT()initializes unit state when Bearer loads the unit into a workspace, even without a render or component invocation.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()
{
var supplied := string(request.query.q, fallback: "<script>")
print(<><p><?= supplied ?></p></>)
}
Output
<p><script></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.
There are no exceptions. A function that can fail returns a result map with ok, and a broken program contract stops the request.
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.