Skip to content

TodoMVC Example

This example demonstrates a full TodoMVC implementation built with Croft, showcasing:

  • createHook for reactive todo state and localStorage persistence
  • html template with reactive references and conditional rendering
  • Directives for class toggling, conditional visibility, event handlers
  • defineComponent for reusable UI components
  • Routing with hash-based filter switching

Source Structure

example/
├── index.html              # HTML template (TodoMVC spec)
└── src/
    ├── main.ts             # Entry point
    ├── store.ts            # Reactive store with persistence
    ├── router.ts           # Hash-based routing
    └── components/
        ├── todo-app.ts     # Main app component
        └── todo-footer.ts  # Footer with counter/filters

Store

The store uses createHook with immutable updates and watch for cross-property derived state and localStorage persistence:

typescript
const store = createHook({
  todos: [],
  filter: "all",
  filteredTodos: [],
});

// Persistence
watch(store.$todos, saveTodos);

// Derived: recompute filteredTodos when todos or filter change
function updateFiltered(_value: any, state: Record<string, any>) {
  const { todos, filter } = state;
  if (filter === "active") store.filteredTodos = todos.filter((t) => !t.completed);
  else if (filter === "completed") store.filteredTodos = todos.filter((t) => t.completed);
  else store.filteredTodos = todos;
}
watch(store.$todos, updateFiltered);
watch(store.$filter, updateFiltered);

Key Patterns

RequirementCroft Feature
Conditional section visibility:show=${hasTodos} with a trap: store.$todos((t) => t.length > 0)
Class togglingclass:completed=${todo.completed}
Event handlingonKeydown=${handler}, onDblclick=${handler}
List rendering${store.$filteredTodos.map(todo => renderItem(todo))} (body placement)
Hash routinghashchange event → store.filter
localStoragewatch(store.$todos, ...)

Live App

You can find the full source code at example/ in the repository.