Site Update in progress. Please excuse any potential mess. 😊
Menu

ToDo List with Signals

Port of the original Preact/signals ToDo example Playground

memo logic added by @cucumba :

abc

import { h, render } from "preact"
import { memo } from "preact/compat"
import { signal, computed } from "preact/signals"

const todos = signal([
    { text: "Write my first post", completed: true },
    { text: "Buy new groceries", completed: false },
    { text: "Walk the dog", completed: false },
]);

const completedCount = computed(() => {
    return todos.value.filter(todo => todo.completed).length
});

const newItem = signal("")

function addTodo() {
    todos.value = [...todos.value, { text: newItem.value, completed: false }]
    newItem.value = "" // Reset input value on add
}

function removeTodo(index) {
    todos.value.splice(index, 1)
    todos.value = [...todos.value]
}

function areEqual(prevProps, nextProps) {
    if (prevProps.todo.completed !== nextProps.todo.completed) {
        return false
    }

    if (prevProps.todo.text !== nextProps.todo.text) {
        return false
    }

    if (prevProps.index !== nextProps.index) {
        return false
    }
    return true
}

const TodoItem = memo(({ todo, index }) => {
    // log(todo.text); // should only be called for new/changed elements
    const onCheckboxChange = (event) => {
        const newTodo = { ...todo, completed: event.target.value }
        todos.value = [
            ...todos.value.slice(0, index),
            newTodo,
            ...todos.value.slice(index + 1),
        ]
    }
    return (
        <div class="flex-row items-center mb-2">
            <toggle onValueChanged={onCheckboxChange} value={todo.completed} />
            {todo.completed ? <s>{todo.text}</s> : todo.text}{" "}
            <button onClick={() => removeTodo(index)} text="X" />
        </div>
    )
}, areEqual)

function TodoList() {
    const onInput = event => (newItem.value = event.target.value);
    const elements = todos.value.map((todo, index) => (
        <TodoItem key={index} todo={todo} index={index} />
    ))
    return (
        <div class="p-2">
            <div class="flex-row">
                <textfield class="grow" text={newItem.value} onInput={onInput} />
                <button class="shrink" onClick={addTodo} text="Add" />
            </div>
            <div class="p-2">
                <ul>{elements}</ul>
                <p>{`Completed count: ${completedCount.value}`}</p>
            </div>
        </div>
    )
}

render(<TodoList />, document.body)