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.

You create a new reactive value using Cell.source():

import { Cell } from 'retend';

const isOn = Cell.source(false);

Getting and Setting Values

To read a value from a Cell, call .get(). To update it, call .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 using a Cell in JSX, pass the Cell itself, not its value. Do not call .get() in the JSX expression.

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

Use Cell.derived() for 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 figures out what you depend on by watching .get() calls.
  • Read-only. You can't call .set() on a derived Cell. They're for computing, not storing.
  • Keep them pure. Don't mutate the DOM, make network requests, or call .set() on other Cells inside a derivation.
  • Hoist them before JSX. Create the derived Cell in the component scope, then pass that Cell into JSX instead of calling Cell.derived() inline.

Side Effects with .listen()

Use .listen() to run 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 you call .set() on the Cell, your 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.