First React App - Build a Counter & Todo List!
Build your first real React apps! We'll make a counter and a todo list from scratch, step by step!
Introduction
Time to build! We'll make two classic beginner apps: a counter and a todo list! Let's go!
What You Will Learn
- Using
useStatehook - Handling events
- Rendering lists
- Basic React patterns
Prerequisites
Project 1: React Counter
Let's start simple!
Step 1: Create the Counter Component
In src/App.jsx:
import { useState } from 'react';
function Counter() {
// Declare a state variable called count, starting at 0
const [count, setCount] = useState(0);
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<h1>React Counter</h1>
<h2 style={{ fontSize: '3rem' }}>{count}</h2>
<div>
<button
onClick={() => setCount(count - 1)}
style={{ fontSize: '1.5rem', padding: '0.5rem 1rem', margin: '0.5rem' }}
>
-
</button>
<button
onClick={() => setCount(0)}
style={{ fontSize: '1.5rem', padding: '0.5rem 1rem', margin: '0.5rem' }}
>
Reset
</button>
<button
onClick={() => setCount(count + 1)}
style={{ fontSize: '1.5rem', padding: '0.5rem 1rem', margin: '0.5rem' }}
>
+
</button>
</div>
</div>
);
}
export default Counter;
Step 2: Run It!
Save and visit http://localhost:5173! You have a working counter!
Project 2: Todo List (More Advanced!)
Now let's build something cooler! A todo list!
Step 1: Create Todo Component
Replace App.jsx:
import { useState } from 'react';
function TodoApp() {
// State for todos array and new todo input
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
// Handle form submission
function handleSubmit(e) {
e.preventDefault();
if (newTodo.trim()) {
setTodos([...todos, { id: Date.now(), text: newTodo, done: false }]);
setNewTodo('');
}
}
// Toggle todo done/undone
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
}
// Delete a todo
function deleteTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
}
return (
<div style={{ maxWidth: '400px', margin: '2rem auto', padding: '0 1rem' }}>
<h1 style={{ textAlign: 'center' }}>Todo List</h1>
{/* Form to add new todo */}
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo..."
style={{ flex: 1, padding: '0.5rem', fontSize: '1rem' }}
/>
<button type="submit" style={{ padding: '0.5rem 1rem' }}>
Add
</button>
</form>
{/* Todo list */}
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map(todo => (
<li
key={todo.id}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem',
marginBottom: '0.5rem',
backgroundColor: '#f0f0f0',
borderRadius: '4px',
textDecoration: todo.done ? 'line-through' : 'none',
opacity: todo.done ? 0.7 : 1
}}
>
<span onClick={() => toggleTodo(todo.id)} style={{ cursor: 'pointer' }}>
{todo.text}
</span>
<button
onClick={() => deleteTodo(todo.id)}
style={{ color: 'red', border: 'none', background: 'transparent', cursor: 'pointer' }}
>
✕
</button>
</li>
))}
</ul>
{/* Empty state */}
{todos.length === 0 && <p style={{ textAlign: 'center', color: '#888' }}>No todos yet! Add one above!</p>}
</div>
);
}
export default TodoApp;
Key Concepts We Used
1. useState
We used useState for:
- Counter value
- Todos array
- New todo input
2. Event Handlers
onClickfor buttonsonSubmitfor formsonChangefor inputs
3. Lists & Keys
We used map with unique keys!
4. Immutability
We never mutated state directly! We always used setters and spread operators!
Best Practices
- Keep components small: We could split TodoList into smaller components!
- Use descriptive variable names:
todosnott! - Always use keys with map: Helps React keep track!
- Handle empty states: We showed a message when there are no todos!
Common Mistakes
- Mutating state directly: Never do
todos.push(...)- always usesetTodos! - Not preventing default form behavior: We added
e.preventDefault()! - Forgetting keys: Always add keys when mapping!
Next Steps
From here, you could add:
- Edit todos
- Filter todos (All/Active/Completed)
- LocalStorage persistence
- Due dates
Summary
You just built two React apps! A counter and a todo list! You used useState, event handlers, and lists - the building blocks of React!
Related Articles
Previous Tutorial
Next Tutorial
Let's dive deep into JSX! → Introduction to JSX