Skip to content

02 — Todo list

Arrays, keyed reconciliation, and batched updates. This is where fine-grained reactivity earns its keep — only the changed <li> updates, not the whole list.

tsx
import { use } from 'flexium/core'

type Todo = { id: number; text: string; done: boolean }

let nextId = 1

export function TodoList() {
  const [todos, setTodos] = use<Todo[]>([])
  const [draft, setDraft] = use('')

  function add() {
    if (!draft.trim()) return
    setTodos(prev => [...prev, { id: nextId++, text: draft, done: false }])
    setDraft('')
  }

  function toggle(id: number) {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t))
  }

  function remove(id: number) {
    setTodos(prev => prev.filter(t => t.id !== id))
  }

  return (
    <div>
      <input
        value={draft}
        oninput={(e: any) => setDraft(e.target.value)}
        onkeydown={(e: any) => { if (e.key === 'Enter') add() }}
        placeholder="What needs doing?"
      />
      <button onclick={add}>Add</button>

      <ul>
        {todos.map(t => (
          <li key={t.id} style={{ textDecoration: t.done ? 'line-through' : 'none' }}>
            <input type="checkbox" checked={t.done} onchange={() => toggle(t.id)} />
            {t.text}
            <button onclick={() => remove(t.id)}>×</button>
          </li>
        ))}
      </ul>

      <p>{todos.filter(t => !t.done).length} remaining</p>
    </div>
  )
}

Why key matters

The key={t.id} attribute lets flexium's renderer (Phase 2's reconcile rewrite) match <li> elements across renders by identity instead of position. When you toggle item 5 in a list of 100, only that one <li> is re-rendered. Without key, the renderer falls back to positional diff and may re-render unrelated nodes.

Batched updates

Multiple setTodos calls in the same synchronous block coalesce into a single flush:

tsx
function checkAll() {
  setTodos(prev => prev.map(t => ({ ...t, done: true })))
  setSomethingElse(...)
  // → 1 microtask flush, 1 DOM patch pass
}

Phase 2's DocumentFragment-based batch insert + Range-based batch remove means even a 1,000-item reordering completes in ~4 ms on Chrome.

Try it

  • Add a "Clear completed" button using a single setTodos(prev => prev.filter(t => !t.done)).
  • Show a <p>You're all caught up!</p> when todos.length === 0 — flexium will create/destroy that node automatically.

API surface used

  • use<T[]>([]) — typed signal
  • <li key={...}> — keyed list reconciliation

Next

Data fetchinguse(async fn), Suspense, ErrorBoundary.

Released under the MIT License.