packages/component/docs/getting-started.md
Create interactive UIs with Remix Component using a two-phase component model: setup runs once, render runs on every update.
To start using Remix Component on the client, create a root and render your top-level component:
import { createRoot } from 'remix/component'
import type { Handle } from 'remix/component'
function App(handle: Handle) {
return () => (
<div>
<h1>Hello, World!</h1>
</div>
)
}
// Create a root attached to a DOM element
let container = document.getElementById('app')!
let root = createRoot(container)
// Render your app
root.render(<App />)
The createRoot function takes a DOM element (or document.body) and returns a root object with a render method. You can call render multiple times to update the app:
function App(handle: Handle) {
let count = 0
return () => (
<div>
<h1>Count: {count}</h1>
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
Increment
</button>
</div>
)
}
let root = createRoot(document.body)
root.render(<App />)
The root object provides several methods:
render(node) - Renders a component tree into the root containerflush() - Synchronously flushes all pending updates and tasksdispose() - Removes the component tree and cleans uplet root = createRoot(document.body)
// Render initial app
root.render(<App />)
// Flush any pending updates synchronously
root.flush()
// Later, remove the app
root.dispose()
For a server-rendered app, define your page as a component, render it with renderToStream, and hydrate client entries on the client:
import { renderToStream } from 'remix/component/server'
import { Frame } from 'remix/component'
import { Counter } from './assets/counter.tsx'
function App() {
return () => (
<html>
<head>
<title>My App</title>
<script async type="module" src="/assets/entry.js" />
</head>
<body>
<h1>Hello</h1>
<Counter setup={0} label="Clicks" />
<Frame src="/sidebar" fallback={<div>Loading...</div>} />
</body>
</html>
)
}
let stream = renderToStream(<App />, {
resolveFrame: (src) => fetchFrameHtml(src),
})
return new Response(stream, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})
// assets/entry.tsx
import { run } from 'remix/component'
let app = run({
async loadModule(moduleUrl, exportName) {
let mod = await import(moduleUrl)
return mod[exportName]
},
async resolveFrame(src, signal) {
let res = await fetch(src, { headers: { accept: 'text/html' }, signal })
return res.body ?? (await res.text())
},
})
await app.ready()
// assets/counter.tsx
import { clientEntry, on, type Handle } from 'remix/component'
export let Counter = clientEntry(
'/assets/counter.js#Counter',
function Counter(handle: Handle, setup: number) {
let count = setup
return (props: { label: string }) => (
<div>
<span>
{props.label}: {count}
</span>
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
+
</button>
</div>
)
},
)
renderToString and renderToStreamclientEntry and run<Frame>