Solid.js To Do App Tutorial

Learn how to create a basic To Do App with Solid.js by example.

Step 1: Create Solid.js App

npm create vite@latest solidjs-todo-app-tutorial -- --template solid-ts
cd solidjs-todo-app-tutorial
npm install
npm run dev

Step 2: Install dependencies

Install lodash for its debounce utility.

npm i lodash
npm i --save-dev @types/lodash

Step 3: Clear out the default styles

Remove everything in App.css and index.css.

Step 4: Add the To Do App code to App.tsx

/src/App.tsx

import { For, Show, createResource } from "solid-js";
import _ from "lodash";

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

const URL = "https://jsonplaceholder.typicode.com";

const [todos, { mutate }] = createResource(fetchTodos);

async function fetchTodos(): Promise<Todo[]> {
  const response = await fetch(`${URL}/todos`);
  return await response.json();
}

async function handleClick(todo: Todo) {
  mutate((prev) => {
    const idx = prev!.findIndex((t) => t.id === todo.id);
    const newState = [...prev!];
    newState.splice(idx, 1, { ...todo, completed: !todo.completed });
    return newState;
  });
  const response = await fetch(`${URL}/todos/${todo.id}`, {
    method: "PATCH",
    body: JSON.stringify({
      completed: !todo.completed,
    }),
  });
  return await response.json();
}

async function handleCreate() {
  const response = await fetch(`${URL}/todos`, {
    method: "POST",
    body: JSON.stringify({
      title: "",
    }),
  });
  const json = await response.json();
  mutate((prev) => {
    const newTodo: Todo = {
      id: json.id,
      title: "",
      completed: false,
    };
    const newState = [newTodo, ...prev!];
    return newState;
  });
}

const debouncedTitleChange = _.debounce(
  async (newTitle: string, todo: Todo) => {
    await fetch(`${URL}/todos/${todo.id}`, {
      method: "PATCH",
      body: JSON.stringify({
        title: newTitle,
      }),
    });
    mutate((prev) => {
      const idx = prev!.findIndex((t) => t.id === todo.id);
      const newState = [...prev!];
      newState.splice(idx, 1, { ...todo, title: newTitle });
      return newState;
    });
  },
  500
);

async function handleTitleChange(e: Event, todo: Todo) {
  const target = e.target as HTMLInputElement;
  debouncedTitleChange(target.value, todo);
}

async function handleDelete(todo: Todo) {
  mutate((prev) => {
    const idx = prev!.findIndex((t) => t.id === todo.id);
    const newState = [...prev!];
    newState.splice(idx, 1);
    return newState;
  });
  await fetch(`${URL}/todos/${todo.id}`, {
    method: "DELETE",
  });
}

function addTenThousandToDos() {
  const newTodos: Todo[] = [];
  for (let i = 0; i < 10000; i++) {
    newTodos.push({ id: 1000 + i, title: `to do ${i}`, completed: false });
  }
  mutate((prev) => {
    return [...newTodos, ...prev!];
  });
}

function App() {
  return (
    <div>
      <h1>Solid.js TODO</h1>
      <Show when={!todos.loading} fallback={<div>Loading...</div>}>
        <button onClick={handleCreate}>Create New To Do</button>
        <button onClick={addTenThousandToDos}>Add 10,000 To Dos</button>
        <ul class="todo-list">
          <For each={todos()}>
            {(todo) => (
              <li>
                <input
                  type="checkbox"
                  checked={todo.completed}
                  onChange={() => handleClick(todo)}
                />{" "}
                <input
                  type="text"
                  value={todo.title}
                  onInput={(e) => handleTitleChange(e, todo)}
                />
                <button onClick={() => handleDelete(todo)}>delete</button>
              </li>
            )}
          </For>
        </ul>
      </Show>
    </div>
  );
}

export default App;

Notes

  • jsonplaceholder is used as the fake API for our To Do App.
  • The createResource primitive is used to create a signal that reflects the result of an async request.
  • handleClick is an async function that updates the state of the todos resource and sends a PATCH request to the fake API.
  • handleCreate is an async function that sends a POST request to the fake API and adds the new Todo object to the resource.
  • mutate is used to update the resource. Note that the ... spread operator is used to create a new array. The UI is updated when the state of the resource is updated by mutate.
  • debouncedTitleChange is an async function wrapped by lodash's debounce to update the title of a todo. This is to limit the number of requests sent to the server.
  • handleTitleChange is an async function triggered by updating a Todo's title. Parameters are passed to the debounced function.
  • addTenThousandToDos is a function that we will use in an upcoming benchmarking test.
  • The Show component is used to conditionally render part of the view.
  • The For component is used to render a list.

References