Chapter 9: Deleting Todos
Let’s add a delete button to each todo so users can remove items one at a time.
Adding the Delete Handler
First, create the delete function in App:
const deleteTodo = (id: number) =>
todos.update(items => items.filter(t => t.id !== id));
This filters out the todo with the matching ID, effectively removing it. The function returns an Effect.
Updating TodoItem Props
Update src/components/TodoItem.ts to accept an onDelete callback:
import { $, collect, Readable } from "@effex/dom";
import { Effect } from "effect";
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoItemProps {
todo: Readable<Todo>;
onToggle: (id: number) => Effect.Effect<void>;
onDelete: (id: number) => Effect.Effect<void>;
}
export const TodoItem = (props: TodoItemProps) =>
Effect.gen(function* () {
const { todo, onToggle, onDelete } = props;
const todoId = yield* Readable.map(todo, t => t.id).get;
return yield* $.li(
{
class: Readable.map(todo, t =>
t.completed ? "todo-item completed" : "todo-item"
),
},
collect(
$.input({
type: "checkbox",
class: "toggle",
checked: Readable.map(todo, t => t.completed),
onChange: () => onToggle(todoId),
}),
$.span({ class: "todo-text" }, $.of(Readable.map(todo, t => t.text))),
$.button(
{
class: "delete-btn",
onClick: () => onDelete(todoId),
},
$.of("×")
),
)
);
});
We added:
onDeleteprop- A delete button with an × character
Passing onDelete to TodoItem
Update the each in main.ts:
each(filteredTodos, {
key: (todo) => todo.id,
render: (todo) => TodoItem({
todo,
onToggle: toggleTodo,
onDelete: deleteTodo,
}),
})
Styling the Delete Button
Add styles to show the button on hover:
.todo-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #eee;
position: relative;
}
.delete-btn {
position: absolute;
right: 12px;
background: none;
border: none;
color: #cc9a9a;
font-size: 24px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s, color 0.2s;
}
.todo-item:hover .delete-btn {
opacity: 1;
}
.delete-btn:hover {
color: #af5b5e;
}
The delete button is hidden by default and appears when hovering over the todo item.
Try It Out
- Hover over a todo item
- Click the × button
- The todo disappears instantly
- The count updates automatically
How It Works
The flow is simple:
- User clicks delete button →
onDelete(todoId)called deleteTodoruns →todos.update(items => items.filter(...))todosSignal updates with the item removedeachdetects the change and removes the DOM element- All derived values (count, hasCompleted, etc.) update automatically
Key Takeaways
- Delete by filtering the item out of the array
- Pass handlers down just like toggle
- CSS hover states work great for revealing actions
- The DOM updates automatically when items are removed