Todo List App With React
This is my attempt at trying to explain how I built my first React project.

I work as an AI data labeller in the automotive industry. But my goal is to become a professional Front-end web developer.
A few weeks ago I started learning ReactJs. I first watched a crash course to get a brief understanding of what it is all about.
Then I went to the official docs to start fiddling with it. After reading a fair bit of it, I decided to start building small projects and this is one of them.
Let's get started.
The features
Alerts the user to enter a task if an empty task is submitted
Clicking the checkbox against the task marks it as done by crossing a grey line through
The clear completed button clears all tasks that are marked as done
The clear all tasks button clears all tasks in the list whether completed or not
Here is the demo of the app. And here is the project on github.
The complete CSS file.
Import Statements
import { useState } from 'react';
import './Style.css';
In React, you can either build “top-down” by starting with building the components higher up in the hierarchy or “bottom-up” by working from components lower down.
So I started writing the components using the bottom-up approach.
The app component
function ToDoApp() {
return (
<ToDoContainer />
);
}
export default ToDoApp;
Here, I created the ToDoApp function that returns the component that contains all the components of the entire app, ToDoContainer.
The Todo Container
function ToDoContainer() {
// the rest of the code
}
I put all the states and the functions for the entire app in this component and then I passed them down through props to the child components. But more on that later.
For now, let's write the entire UI (user interface) first as it requires just a little effort of thinking before adding interactivity.
The rest of the components
The Header component.
function Header() {
return (
<h1 className='header'>Ta-Da List App</h1>
)
}
The AddTask component.
function AddTask() {
return (
<form className='add-task'>
<input type='text'/>
<button className='add-task-button'>Add Task</button>
</form>
)
}
Here we are returning a simple form with an input box and a button to add a task to the list.
The TaskList component
function TaskList() {
return (
<ul className='task-list-ul'>
<li className='task-list-li'></li>
</ul>
)
}
The TodoControls component returns the clear completed and clear all task buttons.
function TodoControls() {
return (
<div className='tada-wrapper'>
<button className='btn'>Clear all task</button>
<button className='btn'>Clear completed</button>
</div>
)
}
The UI is done.
As I mentioned earlier, I used the bottom-up approach to write the UI. That means the first component (ToDoContainer) we created at the top is in fact at the very bottom of the app and the last one is at the top.
I then added all of the above components in the ToDoContainer to display the static app.
function ToDoContainer() {
return (
<div className='container'>
<Header />
<AddTask />
<TaskList />
<TodoControls />
</div>
)
}
This is the result so far.

Add interactivity
I created three state variables for the tasks, the input value and the checkbox in the ToDoContainer. These are the three pieces of the app that change based on user interaction.
Thereafter I added
function ToDoContainer() {
const [tasks, setTasks] = useState([])
const [inputValue, setInputValue] = useState('')
const [checkedTask, setCheckedTask] = useState([])
// the rest of the code
}
Let us wire the handler functions
I first created the handleChange function that will trigger when clicked in the input.
function handleChange(e) {
setInputValue(e.target.value)
}
Let's create the handler function that will add the input value to the task list.
function handleSubmit(e) {
e.preventDefault()
if (inputValue.trim() !== '') {
setTasks([...tasks, {text: inputValue, completed: false}])
setInputValue('')
} else {
alert('Please enter task')
}
}
This handleSubmit function ensures that a new task is added to the To-Do list only if the user has entered a non-empty task. If the input is empty, it displays an alert prompting the user to enter a task.
Let's create the handleToggleTask function that is responsible for toggling the completion status of a task.
function handleToggleTask(index) {
const updatedTasks = tasks.map((task, i) =>
i === index ? {...task, completed: !task.completed} : task
)
setTasks(updatedTasks)
if (checkedTask.includes(index)) {
setCheckedTask(checkedTask.filter((i) => i !== index))
} else {
setCheckedTask(checkedTask.concat(index))
}
}
This function toggles the completion status of a task, updates the tasks array with the modified task(s), and updates the checkedTask array to keep track of completed tasks.
function handleClearCompletedTask() {
let completedTask = tasks.filter((_, index) => !checkedTask.includes(index))
setTasks(completedTask)
setCheckedTask([])
}
function handleClearAllTask() {
tasks.splice(0, tasks.length)
setTasks([])
}
The handleClearCompletedTask removes completed tasks by filtering the tasks array based on the indices stored in checkedTask.
And the handleClearAllTask removes or clears all the tasks from the to-do list.
Pass the functions as props
Let us go back to the AddTask component and add the state and the handle functions as props.
function AddTask(props) {
const {inputValue, handleChange, handleSubmit} = props
return (
<form className='add-task'>
<input
type='text'
value={inputValue}
onChange={handleChange}
placeholder='Enter task...'
className='add-task-input'
/>
<button
onClick={handleSubmit}
className='add-task-button'>Add Task
</button>
</form>
)
}
We are using "destructuring assignment" to destructure the inputValue, handleChange and handleSubmit properties from the props object.
Now, let's go back to our task list and fill out what was missing for the functionality.
function TaskList({tasks, onToggleTask}) {
return (
<ul className='task-list-ul'>
{tasks.map((task, index) => (
<li key={index} className='task-list-li'>
<input
type='checkbox'
id={`task-checkbox-${index}`}
className='task-checkbox'
checked={task.completed}
onChange={() => onToggleTask(index)}
/>
<label
htmlFor={`task-checkbox-${index}`}
className='task-checkbox-label'>
{' '} {task.text}
</label>
</li>
))}
</ul>
)
}
Now our TaskList component renders the list of tasks. It maps over the tasks array, creating a list item for each task with a checkbox input and a corresponding label. The checkbox's state is determined by the completed property of each task. The onToggleTask function is called when a checkbox is clicked, passing the index of the task.
function TodoControls({onClearCompletedTask, onClearAllTask}) {
return (
<div className='tada-wrapper'>
<button
className='btn'
onClick={onClearAllTask}
>Clear all task
</button>
<button
className='btn'
onClick={onClearCompletedTask}>
Clear completed
</button>
</div>
)
}
In this component, we are receiving the functions to clear tasks as props.
The complete code
import { useState } from 'react';
import './Style.css';
function TodoControls({onClearCompletedTask, onClearAllTask}) {
return (
<div className='tada-wrapper'>
<button
className='btn'
onClick={onClearAllTask}>Clear all task</button>
<button
className='btn'
onClick={onClearCompletedTask}>
Clear completed
</button>
</div>
)
}
function TaskList({tasks, onToggleTask}) {
return (
<ul className='task-list-ul'>
{tasks.map((task, index) => (
<li key={index} className='task-list-li'>
<input
type='checkbox'
id={`task-checkbox-${index}`}
className='task-checkbox'
checked={task.completed}
onChange={() => onToggleTask(index)}
/>
<label htmlFor={`task-checkbox-${index}`}
className='task-checkbox-label'>
{' '} {task.text}
</label>
</li>
))}
</ul>
)
}
function AddTask(props) {
const {inputValue, handleChange, handleSubmit} = props
return (
<form className='add-task'>
<input
type='text'
value={inputValue}
onChange={handleChange}
placeholder='Enter task...'
className='add-task-input'
/>
<button
onClick={handleSubmit}
className='add-task-button'>Add Task
</button>
</form>
)
}
function Header() {
return (
<h1 className='header'>Ta-Da List App</h1>
)
}
function ToDoContainer() {
const [tasks, setTasks] = useState([])
const [inputValue, setInputValue] = useState('')
const [checkedTask, setCheckedTask] = useState([])
function handleChange(e) {
setInputValue(e.target.value)
}
function handleSubmit(e) {
e.preventDefault()
if (inputValue.trim() !== '') {
setTasks([...tasks, {text: inputValue, completed: false}])
setInputValue('')
} else {
alert('Please enter task')
}
}
function handleToggleTask(index) {
const updatedTasks = tasks.map((task, i) =>
i === index ? {...task, completed: !task.completed} : task
)
setTasks(updatedTasks)
if (checkedTask.includes(index)) {
setCheckedTask(checkedTask.filter((i) => i !== index))
} else {
setCheckedTask(checkedTask.concat(index))
}
}
function handleClearCompletedTask() {
let completedTask = tasks.filter((_, index) => !checkedTask.includes(index))
setTasks(completedTask)
setCheckedTask([])
}
function handleClearAllTask() {
tasks.splice(0, tasks.length)
setTasks([])
}
return (
<div className='container'>
<Header />
<AddTask
inputValue={inputValue}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
<TaskList
tasks={tasks}
onToggleTask={handleToggleTask}
/>
<TodoControls
onClearCompletedTask={handleClearCompletedTask}
onClearAllTask={handleClearAllTask}
/>
</div>
)
}
function ToDoApp() {
return (
<ToDoContainer />
);
}
export default ToDoApp;
We are finally done.
I may have not done a good job with this but I will keep it going to get better at it.
Thanks for reading.




