Todo List App
In this tutorial, we'll create a simple to-do list application with the following features:
- Add todo items
- Edit todo items
- Delete todo items
- Filter todo items
- Display useful stats
🔹 Step 1: Setup App in root project folder (src/App.tsx
):
src/App.tsx
import { useState } from "react";
import { todoState, Todo } from "./store/state";
import TodoList from "./components/TodoList";
import TodoStats from "./components/TodoStats";
import TodoFilters from "./components/TodoFilters";
export default function App() {
const [text, setText] = useState("");
const todos = todoState.useState() as Todo[];
const addTodo = (text: string) => {
todoState.set([...todos, { id: Date.now(), text, completed: false }]);
};
return (
<div>
<h1>Todo List</h1>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="Add a new todo" />
<button onClick={() => { addTodo(text); setText(""); }}>Add</button>
<TodoFilters />
<TodoList />
<TodoStats />
</div>
);
}
Create multiple states​
🔹 Step 2: Setup multiple states in src/store/state.ts
:
src/store/state.ts
import { useStateGlobal } from "state-jet";
export type Todo = {
id: number;
text: string;
completed: boolean;
};
export const todoState = useStateGlobal<Todo[]>("todos", []);
export const filterState = useStateGlobal<"all" | "completed" | "pending">("filter", "all");
Create components​
🔹 Step 3: Setup Todo components (src/components
):
src/components/TodoList.tsx
import { todoState, filterState, Todo } from "../store/state";
import TodoItem from "./TodoItem";
export default function TodoList() {
const todos = todoState.useState() as Todo[];
const filter = filterState.useState();
const filteredTodos = todos.filter((todo: Todo) => {
if (filter === "completed") return todo.completed;
if (filter === "pending") return !todo.completed;
return true;
});
return (
<ul>
{filteredTodos.map((todo: Todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
Create a file at src/components/TodoItem.tsx
:
src/components/TodoItem.tsx
import { useState } from "react";
import { Todo, todoState } from "../store/state";
export default function TodoItem({ todo }: { todo: Todo }) {
const [isEditing, setIsEditing] = useState(false);
const [newText, setNewText] = useState(todo.text);
const todos = todoState.useState() as Todo[];
const toggleTodo = (id: number) => {
todoState.set(todos.map((todo: Todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const editTodo = (id: number, newText: string) => {
todoState.set(todos.map((todo: Todo) =>
todo.id === id ? { ...todo, text: newText } : todo
));
};
const deleteTodo = (id: number) => {
todoState.set(todos.filter((todo: Todo) => todo.id !== id));
};
return (
<li>
<input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} />
{isEditing ? (
<input value={newText} onChange={e => setNewText(e.target.value)} />
) : (
<span style={{ textDecoration: todo.completed ? "line-through" : "none" }}>{todo.text}</span>
)}
{isEditing ? (
<button onClick={() => { editTodo(todo.id, newText); setIsEditing(false); }}>Save</button>
) : (
<button onClick={() => setIsEditing(true)}>Edit</button>
)}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
);
}
Create a file at src/components/TodoFilters.tsx
:
src/components/TodoFilters.tsx
import { filterState } from "../store/state";
export default function TodoFilters() {
const filter = filterState.useState();
return (
<div style={{ margin: "1rem 0" }}>
<button onClick={() => filterState.set("all")} disabled={filter === "all"}>All</button>
<button onClick={() => filterState.set("completed")} disabled={filter === "completed"}>Completed</button>
<button onClick={() => filterState.set("pending")} disabled={filter === "pending"}>Pending</button>
</div>
);
}
Create a file at src/components/TodoStats.tsx
:
src/components/TodoStats.tsx
import { todoState, Todo } from "../store/state";
export default function TodoStats() {
const todos = todoState.useState() as Todo[];
const completed = todos.filter((todo: Todo) => todo.completed).length;
return (
<div>
<p>Total Todos: {todos.length}</p>
<p>Completed: {completed}</p>
<p>Pending: {todos.length - completed}</p>
</div>
);
}
For the complete code and demo, refer to the Codesandbox example below.