Building a Todo App with React and TypeScript
Learn to build a fully functional todo application with React and TypeScript. Perfect for beginners looking to understand state management and component composition.
Tutorial Info
Prerequisites
- Basic HTML/CSS knowledge
- JavaScript fundamentals
- Node.js installed
What You'll Learn
- Build React components with TypeScript
- Manage component state effectively
- Handle user events
- Persist data with localStorage
- Filter and manipulate arrays
Building a Todo App with React and TypeScript
Welcome to this comprehensive tutorial where we'll build a fully functional todo application using React and TypeScript. This tutorial is perfect for beginners who want to understand the fundamentals of React while learning TypeScript.
What We'll Build
By the end of this tutorial, you'll have created a todo app with the following features:
- Add new todos
- Mark todos as complete
- Delete todos
- Filter todos by status
- Persist data in local storage
Prerequisites
Before we start, make sure you have:
- Node.js installed (version 14 or higher)
- Basic knowledge of HTML, CSS, and JavaScript
- Familiarity with React concepts (components, props, state)
Setting Up the Project
Let's start by creating a new React project with TypeScript:
npx create-react-app todo-app --template typescript cd todo-app npm start
Step 1: Defining Types
First, let's define the TypeScript interfaces for our todo items:
// types/Todo.ts export interface Todo { id: string; text: string; completed: boolean; createdAt: Date; } export type FilterType = 'all' | 'active' | 'completed';
Step 2: Creating the Todo Component
// components/TodoItem.tsx import React from 'react'; import { Todo } from '../types/Todo'; interface TodoItemProps { todo: Todo; onToggle: (id: string) => void; onDelete: (id: string) => void; } const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggle, onDelete }) => { return ( <div className={`todo-item ${todo.completed ? 'completed' : ''}`}> <input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} /> <span className="todo-text">{todo.text}</span> <button onClick={() => onDelete(todo.id)}>Delete</button> </div> ); }; export default TodoItem;
Step 3: Main App Component
// App.tsx import React, { useState, useEffect } from 'react'; import TodoItem from './components/TodoItem'; import { Todo, FilterType } from './types/Todo'; import './App.css'; const App: React.FC = () => { const [todos, setTodos] = useState<Todo[]>([]); const [inputValue, setInputValue] = useState(''); const [filter, setFilter] = useState<FilterType>('all'); // Load todos from localStorage on mount useEffect(() => { const savedTodos = localStorage.getItem('todos'); if (savedTodos) { setTodos(JSON.parse(savedTodos)); } }, []); // Save todos to localStorage whenever todos change useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)); }, [todos]); const addTodo = () => { if (inputValue.trim()) { const newTodo: Todo = { id: Date.now().toString(), text: inputValue, completed: false, createdAt: new Date() }; setTodos([...todos, newTodo]); setInputValue(''); } }; const toggleTodo = (id: string) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )); }; const deleteTodo = (id: string) => { setTodos(todos.filter(todo => todo.id !== id)); }; const filteredTodos = todos.filter(todo => { if (filter === 'active') return !todo.completed; if (filter === 'completed') return todo.completed; return true; }); return ( <div className="app"> <h1>Todo App</h1> <div className="input-section"> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Add a new todo..." onKeyPress={(e) => e.key === 'Enter' && addTodo()} /> <button onClick={addTodo}>Add Todo</button> </div> <div className="filter-section"> <button className={filter === 'all' ? 'active' : ''} onClick={() => setFilter('all')} > All </button> <button className={filter === 'active' ? 'active' : ''} onClick={() => setFilter('active')} > Active </button> <button className={filter === 'completed' ? 'active' : ''} onClick={() => setFilter('completed')} > Completed </button> </div> <div className="todos-list"> {filteredTodos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={toggleTodo} onDelete={deleteTodo} /> ))} </div> <div className="stats"> Total: {todos.length} | Active: {todos.filter(t => !t.completed).length} | Completed: {todos.filter(t => t.completed).length} </div> </div> ); }; export default App;
Step 4: Adding Styles
/* App.css */ .app { max-width: 600px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif; } .input-section { display: flex; gap: 10px; margin-bottom: 20px; } .input-section input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } .input-section button { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } .filter-section { display: flex; gap: 10px; margin-bottom: 20px; } .filter-section button { padding: 5px 15px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; } .filter-section button.active { background-color: #007bff; color: white; } .todo-item { display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid #eee; } .todo-item.completed .todo-text { text-decoration: line-through; opacity: 0.6; } .todo-text { flex: 1; } .stats { margin-top: 20px; text-align: center; color: #666; }
Key Learning Points
- TypeScript Integration: We defined clear interfaces for our data structures
- State Management: Used useState for managing todos, input, and filters
- Effect Hooks: Utilized useEffect for localStorage persistence
- Component Props: Properly typed component props with interfaces
- Event Handling: Implemented various event handlers with proper typing
Next Steps
Now that you have a working todo app, consider these enhancements:
- Add due dates to todos
- Implement drag and drop reordering
- Add categories or labels
- Integrate with a backend API
- Add animations and better styling
Conclusion
Congratulations! You've built a fully functional todo app with React and TypeScript. You've learned about component composition, state management, and how TypeScript can make your React code more robust and maintainable.
The complete source code is available on GitHub, and you can deploy this app to platforms like Netlify or Vercel for free.