Components
The Xote component system for building reactive user interfaces.
Components Overview
Xote provides a lightweight component system for building reactive UIs. Components are functions that return virtual nodes, which are then rendered to the DOM.
Xote supports two syntax styles for building components:
- JSX Syntax: Modern, declarative JSX syntax (recommended)
- Function API: Explicit function calls with labeled parameters
What are Components?
In Xote, a component is simply a function that returns a Node.node. The recommended way to define components is with the component module pattern using @jsx.component:
Component Module Pattern (Recommended)
Use @jsx.component to define components as modules with a make function. This decorator automatically generates the props type from labeled arguments, enabling clean JSX usage:
open Xote
module Greeting = {
@jsx.component
let make = (~name: string) => {
<div>
<h1> {Node.text("Hello, " ++ name ++ "!")} </h1>
</div>
}
}
// Usage in JSX:
<Greeting name="World" />Components without props simply omit the labeled arguments:
module Header = {
@jsx.component
let make = () => {
<header>
<h1> {Node.text("My App")} </h1>
</header>
}
}
// Usage:
<Header />Key points:
- Components are defined as modules with a
makefunction - The
@jsx.componentdecorator transforms labeled arguments (~propName) into a props record type automatically - Components are used in JSX with
<ComponentName prop={value} />syntax - File-level modules can also be components — just add
@jsx.componentto a top-levelmakefunction
Plain JSX Syntax
You can also define components as simple functions without the decorator:
open Xote
let greeting = () => {
<div>
<h1> {Node.text("Hello, Xote!")} </h1>
</div>
}Function API
open Xote
let greeting = () => {
Html.div(
~children=[
Html.h1(~children=[Node.text("Hello, Xote!")], ())
],
()
)
}JSX Configuration
To use JSX syntax, configure your rescript.json:
{
"bs-dependencies": ["xote"],
"jsx": {
"version": 4,
"module": "XoteJSX"
},
"compiler-flags": ["-open Xote"]
}Text Nodes
Static Text
Use Node.text() for static text:
<div>
{Node.text("This text never changes")}
</div>Reactive Text
Use Node.signalText() for text that updates with signals:
let count = Signal.make(0)
<div>
{Node.signalText(() =>
"Count: " ++ Int.toString(Signal.get(count))
)}
</div>The function is tracked, so the text automatically updates when count changes.
Attributes
JSX Props
JSX elements support common HTML attributes:
class- CSS classes (note:class, notclassName)id- Element IDstyle- Inline stylestype_- Input type (with underscore to avoid keyword conflict)value- Input valueplaceholder- Input placeholderdisabled- Boolean disabled statechecked- Boolean checked state
<button
class="btn btn-primary"
type_="button"
disabled={true}>
{Node.text("Submit")}
</button>Static Attributes (Function API)
Html.button(
~attrs=[
Node.attr("class", "btn btn-primary"),
Node.attr("type", "button"),
Node.attr("disabled", "true"),
],
()
)Reactive Attributes
Function API supports reactive attributes:
let isActive = Signal.make(false)
Html.div(
~attrs=[
Node.computedAttr("class", () =>
Signal.get(isActive) ? "active" : "inactive"
)
],
()
)Event Handlers
JSX Event Props
JSX elements support common event handlers:
onClick- Click eventsonInput- Input eventsonChange- Change eventsonSubmit- Form submit eventsonFocus,onBlur- Focus eventsonKeyDown,onKeyUp- Keyboard events
let count = Signal.make(0)
let increment = (_evt: Dom.event) => {
Signal.update(count, n => n + 1)
}
<button onClick={increment}>
{Node.text("+1")}
</button>Lists
Simple Lists (Non-Keyed)
Use Node.list() for simple lists where the entire list re-renders on any change:
let items = Signal.make(["Apple", "Banana", "Cherry"])
<ul>
{Node.list(items, item =>
<li> {Node.text(item)} </li>
)}
</ul>Note: Simple lists re-render completely when the array changes (no diffing). For better performance, use keyed lists.
Keyed Lists (Efficient Reconciliation)
Use Node.listKeyed() for efficient list rendering with DOM element reuse:
type todo = {id: int, text: string, completed: bool}
let todos = Signal.make([
{id: 1, text: "Buy milk", completed: false},
{id: 2, text: "Walk dog", completed: true},
])
<ul>
{Node.listKeyed(
todos,
todo => todo.id->Int.toString, // Key extractor
todo => <li> {Node.text(todo.text)} </li> // Renderer
)}
</ul>Benefits of keyed lists:
- Reuses DOM elements - Only updates what changed
- Preserves component state - When list items move position
- Better performance - Fewer DOM operations for large lists
- Efficient reconciliation - Adds/removes/moves only necessary elements
Best practices:
- Always use unique, stable keys (like database IDs)
- Don't use array indices as keys
- Keys should be strings
- Use listKeyed for any list that can be reordered, filtered, or modified
Mounting to the DOM
Use mountById to attach your component to an existing DOM element:
let app = () => {
<div> {Node.text("Hello, World!")} </div>
}
Node.mountById(app(), "app")Example: Counter Component
Here's a complete counter component using the component module pattern:
open Xote
module Counter = {
@jsx.component
let make = (~initialValue: int) => {
let count = Signal.make(initialValue)
let increment = (_evt: Dom.event) => {
Signal.update(count, n => n + 1)
}
let decrement = (_evt: Dom.event) => {
Signal.update(count, n => n - 1)
}
<div class="counter">
<h2>
{Node.signalText(() =>
"Count: " ++ Int.toString(Signal.get(count))
)}
</h2>
<div class="controls">
<button onClick={decrement}>
{Node.text("-")}
</button>
<button onClick={increment}>
{Node.text("+")}
</button>
</div>
</div>
}
}
// Use the component in JSX
module App = {
@jsx.component
let make = () => {
<Counter initialValue={10} />
}
}
Node.mountById(App.make({}), "app")Best Practices
- Keep components small: Each component should do one thing well
- Use signals for local state: Create signals inside components for component-specific state
- Pass data via props: Use record types for component parameters
- Compose components: Build complex UIs from simple, reusable components
- Choose the right list type: Use
listKeyedfor dynamic lists,listfor simple static lists - Use class not className: In JSX, use the
classprop for CSS classes
Next Steps
- Try the Demos to see components in action
- Learn about Routing for building SPAs
- Explore the API Reference for detailed documentation