Mikata

Your first app

A full todo list in one file. This is idiomatic Mikata - the component function runs once, signals drive updates, and each()/show() handle reactive lists and conditional branches without re-running the surrounding component.

import { signal, computed, render, show, each } from 'mikata';

function TodoList() {
  const [todos, setTodos] = signal<{ id: number; text: string; done: boolean }[]>([]);
  const [input, setInput] = signal('');
  const remaining = computed(() => todos().filter((t) => !t.done).length);

  const add = () => {
    if (!input().trim()) return;
    setTodos([...todos(), { id: Date.now(), text: input(), done: false }]);
    setInput('');
  };

  const toggle = (id: number) =>
    setTodos(todos().map((t) => (t.id === id ? { ...t, done: !t.done } : t)));

  return (
    <div>
      <input
        value={input()}
        onInput={(e) => setInput(e.currentTarget.value)}
        onKeydown={(e) => e.key === 'Enter' && add()}
      />
      <button onClick={add}>Add</button>

      <ul>
        {each(todos, (todo) => (
          <li
            style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
            onClick={() => toggle(todo.id)}
          >
            {todo.text}
          </li>
        ))}
      </ul>

      {show(
        () => todos().length > 0,
        () => <p>{remaining()} remaining</p>
      )}
    </div>
  );
}

render(() => <TodoList />, document.getElementById('app')!);

What's happening

  • signal returns [getter, setter]. The getter is a function - call it to read, and Mikata tracks the read in the surrounding reactive context.
  • computed is a derived signal. It re-runs only when a dependency it read changes.
  • each renders a keyed reactive list without re-creating rows. show does the same for a single branch.
  • Event handlers receive native events, typed against the element they're bound to - e.currentTarget.value is typed for you.