Reactivity and Cells
What is Reactivity?
Application data can change in response to user input, completed requests, or timers. Reactivity is the mechanism that updates the interface when this data changes.
Retend implements reactivity with Cells. A Cell stores a value and updates the rendered locations that use it when the value changes.
A reactive value is created with Cell.source():
import { Cell } from 'retend'; const isOn = Cell.source(false);
Getting and Setting Values
A value is read from a Cell with .get(), and updated with .set():
import { Cell } from 'retend'; const isOn = Cell.source(false); console.log(isOn.get()); // → false isOn.set(true); console.log(isOn.get()); // → true
Using Cells in JSX
When a Cell is used in JSX, the Cell itself is passed, not its value. Calling .get() in the JSX expression isn't necessary.
import { Cell } from 'retend'; function ToggleSwitch() { const isOn = Cell.source(false); const toggle = () => { isOn.set(!isOn.get()); }; return ( <div> {/* Pass the Cell directly */} <p>Status: {isOn}</p> <button type="button" onClick={toggle}> Toggle </button> </div> ); }
Passing a Cell into JSX creates a binding to the rendered output. Each call to .set() updates the corresponding text node. The ToggleSwitch function does not run again.
Derived Cells for Computed Values
Cell.derived() produces a value computed from other Cells. For example, fullName can depend on firstName and lastName.
import { Cell } from 'retend'; function UserProfile() { const firstName = Cell.source('Alice'); const lastName = Cell.source('Smith'); const fullName = Cell.derived(() => { return `${firstName.get()} ${lastName.get()}`; }); return <h1>{fullName}</h1>; }
Cell.derived() runs its function immediately and tracks the Cells read with .get(). Those reads form the dependency list. When either firstName or lastName changes, the function runs again and the derived value updates wherever it is used.
Rules for Derived Cells
- No dependency arrays. Retend determines its dependencies by watching
.get()calls. - Read-only. A derived Cell has no
.set(). They compute values rather than store them. - Pure by design. Derivations don't mutate the DOM, make network requests, or call
.set()on other Cells. - Hoisted before JSX. A derived Cell is created in the component scope and passed into JSX, rather than calling
Cell.derived()inline.
Side Effects with .listen()
.listen() runs code when a Cell changes, such as logging a value, synchronizing localStorage, or performing a manual update:
import { Cell } from 'retend'; const theme = Cell.source('light'); theme.listen((newTheme) => { document.body.className = newTheme; localStorage.setItem('user-theme', newTheme); });
Every time .set() is called on the Cell, the listener function runs with the new value. Unlike derived Cells, listeners are designed explicitly for side effects.
Cells can be used to build interactive interfaces.