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.
onSetup
onSetup handles 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>; }
A cleanup function is returned for timers, event listeners, and other resources. It runs when the component is destroyed and prevents those resources from remaining active.
onConnected
onConnected handles code that interacts with a rendered element, such as measuring its size, drawing on a canvas, or attaching a third-party library.
onConnected ties code to a specific element's connection. It receives a reference to an element, and the 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 returned cleanup runs whenever the element is no longer connected. On the web, "connected" means the element is currently part of the DOM tree.
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:
Cell.source()values are defined at the top. - Derived:
Cell.derived()computations are added. - Handlers: Event handler functions (like
onClick) are written. - Lifecycle: The
onSetupandonConnectedblocks are placed. - Return: The JSX is returned last.
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> ); }