Todo Tutorial
Build a simple Todo app using the following core concepts:
- Reactivity
- Templating
- Events
Reactivity
Section titled “Reactivity”Start by creating signals to hold our todos and input value. We don’t need to import computed
when creating or derived signals inside a component, just wrap the logic in a function.
import { signal, effect } from "@hellajs/core";
function Todo() { const todos = signal([]); const inputValue = signal("");
const todoCount = () => todos().length; const hasTodos = () => todoCount() > 0; const isDisabled = () => !inputValue();
effect(() => { console.log("Todo count:", todoCount()); });
// ...rest of the code will go here...}
Now let’s create some functions to setInput
and addTodo
. For this simple example we’re not doing any extra checks like checking for duplicate todos or null event targets.
// ....continued...
const setInput = (event) => { inputValue.set(event.target.value);};
const addTodo = () => { todos.set([...todos(), inputValue()]); inputValue.set("");};
Templating
Section titled “Templating”Create the UI using a simple function templating system. Update the imports to include the ergonomic html proxy helper, we can destructure the elements if desired to use div
instead of html.div
. We also need forEach to loop over the todos and show for conditional rendering.
import { signal, effect } from "@hellajs/core";import { forEach, show, html } from "@hellajs/dom";
const { div, input, button, ul, li, p } = html;
For dynamic attributes and text we need to pass a function reference. There are a couple of ways to handle dynamic string as shown below
// ...continued...
return div( // use template strings p(() => `Todo List (${todoCount()})`), // or the p(...children) spread pattern p("Todo List (", todoCount, ")"), div( input({ type: "text", value: inputValue, oninput: setInput, }), button({ onclick: addTodo, disabled: isDisabled }, "Add"), ), show( hasTodos, ul(forEach(todos, (todo) => li(todo))), div("No todos yet!"), ),);
Mounting
Section titled “Mounting”Add mount to your imports and pass the Todo
function reference. By default, mount looks for #app
element but you can pass any element id as a querySelector.
import { signal, effect } from "@hellajs/core";import { forEach, show, html, mount } from "@hellajs/dom";
const { div, input, button, ul, li, p } = html;
function Todo() { // Todo implementation...}
mount(Todo, '#todo');
Full Example
Section titled “Full Example”import { signal, effect } from "@hellajs/core";import { forEach, show, html, mount } from "@hellajs/dom";
const { div, input, button, ul, li, p } = html;
function Todo() { const todos = signal([]); const inputValue = signal("");
const todoCount = () => todos().length; const hasTodos = () => todoCount() > 0; const isDisabled = () => !inputValue();
effect(() => { console.log("Todo count:", todoCount()); });
const setInput = (event) => { inputValue.set(event.target.value); };
const addTodo = () => { todos.set([...todos(), inputValue()]); inputValue.set(""); };
return div( p("Todo List (", todoCount, ")"), div( input({ type: "text", value: inputValue, oninput: setInput, }), button({ onclick: addTodo, disabled: isDisabled }, "Add"), ), show( hasTodos, ul(forEach(todos, (todo) => li(todo))), div("No todos yet!"), ), );}
mount(Todo, '#todo');