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.