Modules
Stateful module system for Hypen applications
Modules
Modules are the stateful building blocks of Hypen applications. They combine UI templates with TypeScript state management.
Overview
A module consists of:
- Template (
.hypenfile) — The declarative UI - Definition (TypeScript) — State, actions, and lifecycle handlers
module Counter {
Column {
Text("Count: ${state.count}")
Button { Text("+1") }
.onClick(@actions.increment)
}
}import { app } from "@hypen-space/core";
export default app
.defineState({ count: 0 })
.onAction("increment", async ({ state }) => {
state.count++;
});API Reference
defineState
Sets the initial state with TypeScript generics for type safety:
interface MyState {
items: string[];
loading: boolean;
}
app.defineState<MyState>({
items: [],
loading: false
})onCreated
Lifecycle hook called when the module mounts. Receives (state, context?):
.onCreated(async (state, context) => {
const data = await fetchData();
state.items = data;
// Access router
if (context?.router) {
context.router.push("/home");
}
})onDestroyed
Lifecycle hook called when the module unmounts. Receives (state, context?):
.onDestroyed((state, context) => {
clearInterval(state.timerId);
})onAction
Handler for UI actions. Receives a context object with action details, state, and global context:
.onAction<PayloadType>("actionName", async ({ action, state, context }) => {
// action.name - the action name
// action.payload - event data (typed as PayloadType)
// action.sender - component that dispatched the action
// state - current state (auto-synced via Proxy)
// context - GlobalContext for cross-module communication
// context.router - HypenRouter for navigation
})onError
Error handler called when any action or lifecycle hook throws:
.onError(({ error, actionName, lifecycle, state }) => {
console.error(`Error in ${actionName ?? lifecycle}:`, error.message);
return { handled: true }; // suppress error
// or: { rethrow: true } // re-throw
// or: { retry: true } // retry (actions only)
// or: void // default: log + emit
})See Error Handling Guide for details.
onDisconnect / onReconnect / onExpire
Session lifecycle hooks for remote apps. These handle client disconnections and reconnections:
.onDisconnect(({ state, session }) => {
// Client disconnected, but session is alive (within TTL)
})
.onReconnect(({ session, restore }) => {
// Client reconnected with existing session
restore(savedState);
})
.onExpire(({ session }) => {
// Session TTL expired
})See the TypeScript SDK for details.
build
Finalizes the module definition. Required when passing to RemoteServer.module() or HypenModuleInstance:
const myModule = app
.defineState({ count: 0 })
.onAction("increment", ({ state }) => { state.count++; })
.build();ui
For single-file components, embed the template directly using the hypen tagged template literal:
import { app, hypen, state } from "@hypen-space/core";
export default app
.defineState({ count: 0 })
.onAction("increment", ({ state }) => { state.count++; })
.ui(hypen`
Column {
Text("Count: ${state.count}")
Button { Text("+") }
.onClick(@actions.increment)
}
`);The .ui() method calls .build() internally and returns the finalized module definition.
State Management
State is tracked via Proxy — mutations sync automatically:
.onAction<{ item: Item }>("addItem", async ({ action, state }) => {
state.items.push(action.payload!.item);
// UI updates automatically — no callback needed
})Deep Nesting
Nested object mutations are tracked automatically:
.onAction("updateAddress", ({ state }) => {
state.user.profile.address.city = "New York";
// Tracked at path "user.profile.address.city"
})Batch Updates
Multiple synchronous mutations are coalesced into a single UI update. For explicit batching:
import { batchStateUpdates } from "@hypen-space/core";
.onAction("bulkUpdate", ({ state }) => {
batchStateUpdates(state, () => {
state.items = newItems;
state.count = newItems.length;
});
})Best Practices
- Keep state minimal — Only store what the UI needs
- Use TypeScript interfaces — Define state shape for type safety
- Handle async properly — Show loading states during fetches
- Name actions descriptively —
saveDocumentnothandleClick - Use
.bind()for forms — Two-way binding avoids boilerplate action handlers
See Also
- State & Modules Guide — Detailed tutorial with examples
- Error Handling — Error recovery patterns
- TypeScript SDK — Complete SDK reference including component discovery, RemoteServer, and deployment
- Components — All built-in components