Introduction
From Zero to Hero: Building a ToDo App with React (Step-by-Step). Every developer, whether a beginner or seasoned, has encountered the ToDo app. It’s the perfect starting point to understand a framework like React. The simplicity of a ToDo app makes it an ideal project for learning React concepts like components, props, state, event handling, and conditional rendering.
In this blog, we will walk through building a functional ToDo app using React.js. We’ll explore each step in detail so even if you’re completely new to React, you’ll be able to follow along and build your own project by the end.
Table of Contents
1. Setting Up the Project
Before diving into code, we need to set up our development environment.
Prerequisites
- Node.js and npm installed (https://nodejs.org/)
- Basic knowledge of JavaScript and HTML
- Code editor (we recommend VS Code)
Initialize Project with Vite
Vite offers a faster and simpler way to scaffold React projects than Create React App.
npm create vite@latest react-todo -- --template react
cd react-todo
npm install
npm run dev
2. Project Structure
Your initial Vite React app should have the following structure:
react-todo/
├─ public/
├─ src/
│ ├─ App.jsx
│ ├─ index.css
│ └─ main.jsx
├─ index.html
└─ package.json
We’ll organize the app using a components/
folder where we will keep reusable UI parts like the ToDo list and input form.
3. Creating the ToDo Component
Create a new folder called components
in the src
directory. Inside it, create a file named TodoItem.jsx
:
// src/components/TodoItem.jsx
export default function TodoItem({ task, index, onDelete }) {
return (
<div className="todo-item">
<span>{task}</span>
<button onClick={() => onDelete(index)}>Delete</button>
</div>
);
}
This component accepts task
, index
, and onDelete
as props and renders a single ToDo item.
4. Building the Main App Component
Now, let’s use this TodoItem
in our App.jsx
file:
// src/App.jsx
import { useState } from 'react';
import TodoItem from './components/TodoItem';
function App() {
const [tasks, setTasks] = useState([]);
const [input, setInput] = useState('');
const addTask = () => {
if (input.trim() === '') return;
setTasks([...tasks, input]);
setInput('');
};
const deleteTask = (index) => {
const newTasks = [...tasks];
newTasks.splice(index, 1);
setTasks(newTasks);
};
return (
<div className="app">
<h1>My ToDo List</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter a task"
/>
<button onClick={addTask}>Add</button>
{tasks.map((task, index) => (
<TodoItem key={index} task={task} index={index} onDelete={deleteTask} />
))}
</div>
);
}
export default App;
5. Styling the App
Add some basic CSS in index.css
:
.app {
text-align: center;
padding: 20px;
font-family: Arial, sans-serif;
}
input {
padding: 10px;
width: 200px;
margin-right: 10px;
}
button {
padding: 10px 15px;
background-color: #61dafb;
border: none;
cursor: pointer;
}
.todo-item {
margin-top: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
Feel free to enhance this with custom themes or animations.
6. Bonus: Mark Task as Complete
To make our app more interactive, let’s add the ability to mark a task as complete. Update the tasks
state to hold objects instead of strings:
const [tasks, setTasks] = useState([]);
const addTask = () => {
if (input.trim() === '') return;
setTasks([...tasks, { text: input, completed: false }]);
setInput('');
};
const toggleTask = (index) => {
const newTasks = [...tasks];
newTasks[index].completed = !newTasks[index].completed;
setTasks(newTasks);
};
Update TodoItem.jsx
:
export default function TodoItem({ task, index, onDelete, onToggle }) {
return (
<div className="todo-item">
<span
onClick={() => onToggle(index)}
style={{ textDecoration: task.completed ? 'line-through' : 'none' }}
>
{task.text}
</span>
<button onClick={() => onDelete(index)}>Delete</button>
</div>
);
}
And finally update the App.jsx
render logic:
{tasks.map((task, index) => (
<TodoItem
key={index}
task={task}
index={index}
onDelete={deleteTask}
onToggle={toggleTask}
/>
))}
7. Persisting Data with LocalStorage (Optional)
To save tasks even after refreshing the browser, use localStorage
:
useEffect(() => {
const saved = JSON.parse(localStorage.getItem('tasks'));
if (saved) setTasks(saved);
}, []);
useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(tasks));
}, [tasks]);
You’ll need to import useEffect
from React:
import { useState, useEffect } from 'react';
8. Hosting Your App Online
Once done, you can deploy your ToDo app online for free:
- Use Netlify for simple drag and drop deployment.
- Or use Vercel which directly integrates with GitHub repositories.
Conclusion
Congratulations! 🎉 You just built a complete ToDo app in React from scratch. Along the way, you learned about:
- React project structure
- JSX and components
- State and props
- Event handling
- Conditional rendering
- Local storage and persistence
This project is not just a beginner milestone—it’s also a great portfolio piece. You can extend it with more features like categories, due dates, dark mode, or even Firebase authentication.
React is a powerful library, and this ToDo app is just the beginning of what you can create with it. So keep building and keep learning!
Stay tuned for more React tutorials and project ideas. If you liked this blog, feel free to share it and build your version of the app!
Find more React content at: https://allinsightlab.com/category/software-development