Skip to content

Reactive Rendering

Once you've created a reactive object or resource using Starbeam, you can use it in your application using the appropriate Starbeam renderer for your web framework.

Universal Reactivity

Starbeam's universal APIs allow you to create reactive objects and synchronize external state in a way that can be shared across web frameworks. We call these "universal reactive objects".

Framework renderers provide APIs for using universal reactive objects in different web frameworks.

To render something using Starbeam, we first need a reactive object. We'll use a reactive todo list as an example.

  • First, we'll build the reactive todo list using Starbeam's reactive APIs, and without any reference to any specific web framework.
  • Then, we'll see how to use Starbeam renderers to create a todo list UI that will remain up-to-date when the todo list changes.to thyou

The Universal Reactive Object

Consider these reactive class that represent (a) a list of todo items, (b) a todo item:

ts
ts
import * as reactive from "@starbeam/collections";
 
export class TodoItems {
readonly #items = reactive.Map<string, TodoItem>();
 
add(title: string): TodoItem {
const todo = new TodoItem(title);
this.#items.set(todo.id, todo);
return todo;
}
 
toggleAll(checked: boolean): void {
for (const item of this.#items.values()) {
item.toggle(checked);
}
}
 
clearCompleted(): void {
for (const item of this.#items.values()) {
if (item.completed) {
this.#items.delete(item.id);
}
}
}
 
filter(filter: (item: TodoItem) => boolean): TodoItem[] {
return [...this.#items.values()].filter(filter);
}
 
remove(id: string): void {
this.#items.delete(id);
}
}
 
class TodoItem {
readonly id = crypto.randomUUID();
@tracked accessor completed = false;
@tracked accessor title: string;
 
constructor(title: string) {
this.title = title;
}
 
toggle(checked = !this.completed): void {
this.completed = checked;
}
}
ts
import * as reactive from "@starbeam/collections";
 
export class TodoItems {
readonly #items = reactive.Map<string, TodoItem>();
 
add(title: string): TodoItem {
const todo = new TodoItem(title);
this.#items.set(todo.id, todo);
return todo;
}
 
toggleAll(checked: boolean): void {
for (const item of this.#items.values()) {
item.toggle(checked);
}
}
 
clearCompleted(): void {
for (const item of this.#items.values()) {
if (item.completed) {
this.#items.delete(item.id);
}
}
}
 
filter(filter: (item: TodoItem) => boolean): TodoItem[] {
return [...this.#items.values()].filter(filter);
}
 
remove(id: string): void {
this.#items.delete(id);
}
}
 
class TodoItem {
readonly id = crypto.randomUUID();
@tracked accessor completed = false;
@tracked accessor title: string;
 
constructor(title: string) {
this.title = title;
}
 
toggle(checked = !this.completed): void {
this.completed = checked;
}
}

We wrote this reactive todo list using Starbeam's reactive APIs.

Rendering

What we mean when we say that the todo list is "reactive" is that we can create an output UI that is up-to-date with the current state of the todo list.

Starbeam renderers allow you to do just that: take a reactive object and use your framework of choice to render a UI that will remain up-to-date when the reactive object changes.

gts
class TodoList extends Component {
  readonly #todos = new TodoItems();
  @tracked #filter = (todo: TodoItem) => true;

  createTodo = (e: FormEvent) => {
    e.preventDefault();
    const text = e.target["text"].value;
    this.#todos.add(text);
    e.target["text"].value = "";
  };

  <template>
    <header>
      <h1>Todos</h1>
      <form {{on "submit" (this.createTodo)}}>
        <input
          type="text"
          name="text"
          placeholder="What needs to be done?"
        >
        <button type="submit">Create</button>
      </form>
    </header>
    <section class="main">
      <ul class="todo-list">
        {{#each (this.#todos.filter this.filter) as |todo|}}
          <TodoItem @todo={{todo}} @todos={{this.#todos}} />
        {{/each}}
      </ul>
    </section>
  </template>
}

const TodoItem = <template>
  <li>
    <input
        type="checkbox" checked={{@todo.completed}}
        {{on "input" (@todo.completed)}}
    >

    <p>{{@todo.text}}</p>

    <button {{on "click" (@todos.remove todo)}}>
      x
    </button>
  </li>
</template>