Lifecycle Hooks
Retend components run once to build the interface. Lifecycle code generally handles component setup and destruction.
Retend provides onSetup and onConnected. onSetup runs once per component instance, and its cleanup runs when the component is destroyed. onConnected follows a referenced element's connection to the renderer: its callback runs when the element becomes connected, and its cleanup runs when the element is no longer connected. onConnected is supported only by renderers that opt in, such as the DOM renderer in retend-web; in other renderers it is a no-op.
onSetup
Use onSetup for logic that does not need rendered elements, such as starting timers, fetching initial data, or subscribing to external events.
import { onSetup, Cell } from 'retend'; function LiveClock() { const time = Cell.source(new Date()); const timeString = Cell.derived(() => time.get().toLocaleTimeString()); onSetup(() => { // This runs once when the component is created const timerId = setInterval(() => time.set(new Date()), 1000); // The function you return here runs when the component is destroyed return () => clearInterval(timerId); }); return <p>Current time: {timeString}</p>; }
Return a cleanup function for timers, event listeners, and other resources. It runs when the component is destroyed and prevents those resources from remaining active.
onConnected
Use onConnected when code must interact with a rendered element, such as measuring its size, drawing on a canvas, or attaching a third-party library.
onConnected ties your code to a specific element's connection. You give it a reference to an element, and your callback runs with the element itself whenever the element becomes connected. If the element is removed and added back, the callback runs again — and the cleanup you return runs whenever the element is no longer connected. In the DOM renderer, "connected" means the element is part of the document.
import { onConnected, Cell } from 'retend'; function CanvasDrawer() { // 1. Create a reference starting at null const canvasRef = Cell.source<HTMLCanvasElement | null>(null); // 2. Pass the reference to onConnected onConnected(canvasRef, (canvas) => { // At this point, `canvas` is guaranteed to be the actual HTML element const context = canvas.getContext('2d'); context.fillStyle = 'blue'; context.fillRect(10, 10, 150, 100); return () => { // Cleanup runs when the canvas is removed from the page console.log('Canvas is being removed'); }; }); // 3. Attach the reference to your JSX using the `ref` attribute return <canvas ref={canvasRef} width="200" height="200"></canvas>; }
Structuring Your Components
A consistent component structure can make lifecycle-related code easier to read:
- State: Define your
Cell.source()values at the top. - Derived: Add any
Cell.derived()computations. - Handlers: Write your event handler functions (like
onClick). - Lifecycle: Place your
onSetupandonConnectedblocks. - Return: End by returning your JSX.
This order defines the data and handlers before the lifecycle blocks and JSX.
import { Cell, onSetup } from 'retend'; function TodoItem({ item }) { // 1. State const isEditing = Cell.source(false); // 2. Derived const displayText = Cell.derived(() => { return isEditing.get() ? 'Editing...' : item.title; }); // 3. Handlers const toggleEdit = () => isEditing.set(!isEditing.get()); // 4. Lifecycle onSetup(() => { console.log('TodoItem created:', item.id); return () => console.log('TodoItem destroyed:', item.id); }); // 5. Return JSX return ( <div> <span>{displayText}</span> <button type="button" onClick={toggleEdit}> Edit </button> </div> ); }