Todo List
The classic, in full: keyed list rendering, a native form submit, immutable updates and a memoized derived value.
Source
<div pp-component="todo_demo">
<form onsubmit="add(event)">
<input name="title" placeholder="What needs doing?" />
<button type="submit">Add</button>
</form>
<ul>
<template pp-for="todo in todos">
<li key="{todo.id}">
<label>
<input type="checkbox"
checked="{todo.done}"
onchange="toggle(todo.id)" />
<span pp-style="{todo.done ? 'text-decoration: line-through' : ''}">
{todo.title}
</span>
</label>
<button onclick="remove(todo.id)">×</button>
</li>
</template>
</ul>
<p hidden="{todos.length > 0}">Nothing to do 🎉</p>
<p hidden="{todos.length === 0}">{remaining} of {todos.length} remaining</p>
<script>
const [todos, setTodos] = pp.state([
{ id: 1, title: "Read the PulsePoint docs", done: true },
{ id: 2, title: "Ship something", done: false },
]);
const remaining = pp.memo(
() => todos.filter((t) => !t.done).length,
[todos]
);
const add = (event) => {
event.preventDefault();
const form = event.currentTarget;
const title = String(new FormData(form).get("title") || "").trim();
if (!title) return;
setTodos([...todos, { id: Date.now(), title, done: false }]);
form.reset();
};
const toggle = (id) =>
setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
const remove = (id) => setTodos(todos.filter((t) => t.id !== id));
</script>
</div>
Live result
Nothing to do 🎉
{remaining} of {todos.length} remaining
What to notice
- The form uses a native
onsubmitplusFormData— no per-input state, no refs. - Every row is keyed by
todo.id. Toggling one row re-renders that row; unchanged rows are reused by v2's per-row cache. - Updates are immutable:
map,filterand spread produce new arrays for the setter. remainingis a memoized derivation — recomputed only whentodoschanges.- Checkboxes are controlled (
checked="{todo.done}"+onchange), so state stays the single source of truth.
To persist these todos to a server, register addTodo / toggleTodo /
deleteTodo in your route's RPC registry and call them with
pp.rpc() — see Backend Integration.