Skip to content
Lesson 14 of 14

Step 1 of 5 · Reading · ~4 min

Read

Production

Capstone: TodoMVC

TodoMVC is the cross-framework benchmark. React, Vue, Svelte, Solid, Angular, Elm and many others each ship an implementation of the same specification, so you can read the same app in any style. It is small enough to hold in your head and big enough to need add, toggle, edit, remove, filter and clear. In this capstone your program is the whole app minus the browser: commands go in, and the HTML the page would show comes out.

Two pure functions

An app of this kind is two functions and one value.

state   = reduce(state, command)     what happened
html    = view(state)                what to show

The reducer is the only place state changes, and it is a pure function of the old state and one command. The view is a pure function of the state, so the screen can never disagree with the data (lesson 1's promise). A framework sits between the two: it takes the vnode tree your view builds, diffs it against the last one (lesson 5), and patches the DOM. That is why the same reducer and the same view logic can be dropped under React, your own framework, or a server that sends strings, and why this exercise can be checked by comparing text.

Here is where each earlier lesson shows up. The tree-to-HTML step is lesson 1. Parts that are absent when empty, like the footer, are the conditional children of lesson 2. App, TodoItem and Footer are the components of lesson 3, and data-id is the key of lesson 8. Toggling a checkbox is an event (lesson 7) that dispatches a command, and saving the list to storage would be an effect (lesson 10).

The commands

CommandEffect
ADD <text>trim the text; if anything is left, append a todo with the next id, active. Ids start at 1 and are never reused, even after a removal
TOGGLE <id>flip that todo's completed flag
TOGGLE_ALLif every todo (and there is at least one) is completed, make all active; otherwise make all completed
EDIT <id> <text>replace the text with the trimmed text; if nothing is left, remove the todo
REMOVE <id>delete it
CLEAR_COMPLETEDdelete every completed todo
FILTER all|active|completedset which todos are visible; anything else is ignored
DUMPprint the view

An unknown id changes nothing. The filter hides todos; it never changes the counts.

The view

All markup is on one line. For a state with at least one todo:

<section class="todoapp"><header class="header"><h1>todos</h1></header>
  <section class="main"><input id="toggle-all" class="toggle-all" type="checkbox"[ checked]><ul class="todo-list">
    <li[ class="completed"] data-id="1"><div class="view"><input class="toggle" type="checkbox"[ checked]><label>TEXT</label><button class="destroy"></button></div></li>
  </ul></section>
  <footer class="footer"><span class="todo-count"><strong>N</strong> items left</span><ul class="filters">
    <li><a[ class="selected"] href="#/">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li>
  </ul>[<button class="clear-completed">Clear completed</button>]</footer>
</section>

(The line breaks and indentation above are only for reading; the output has none.) Bracketed parts appear only under a condition:

  • the whole main section and the footer exist only when there is at least one todo, whatever the filter;
  • the toggle-all box is checked when every todo is completed;
  • an li is listed only if it passes the filter, and has class="completed" when completed, placed before data-id; its checkbox is checked when completed;
  • N is the number of active todos in all, the word is item when N is 1 and items otherwise (so 0 items left);
  • the anchor of the current filter has class="selected";
  • the clear button appears when at least one todo is completed.

Text goes through the escape from lesson 1, so a todo called <b>x</b> & "y" shows as text and never as markup.

Common mistakes

  • Counting active todos after filtering, so the footer changes when you switch tabs.
  • Reusing an id after a removal, which breaks the key contract.
  • Treating TOGGLE_ALL as a plain flip: with a mix, everything becomes completed, and only an all-completed list becomes all active.
  • Forgetting that the footer vanishes when the last todo is removed, even if a filter is set.
  • Escaping twice or not at all, or putting the class after data-id.
  • Keeping an EDIT that trims to nothing instead of removing the todo.

Your exercise

The starter reads the commands, calls your reducer, prints view(state) on DUMP, and gives you esc. You write apply and view. The hidden tests combine several commands, so it is the capability you built across the course, not one rule, that is being checked.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…