Home/Blog/Programming/React Hooks tutorial: Build a to-do list with React Hooks
Home/Blog/Programming/React Hooks tutorial: Build a to-do list with React Hooks

React Hooks tutorial: Build a to-do list with React Hooks

Christina Kopecky
May 29, 2024
12 min read

Become a Software Engineer in Months, Not Years

From your first line of code, to your first day on the job — Educative has you covered. Join 2M+ developers learning in-demand programming skills.

#

With the release of React 16.8 in 2019, React Hooks finally become available to use in our production applications. This allows React developers to make functional components stateful. Instead of using a class component to hold stateful logic, we can use functional components.

React Hooks are a powerful tool, and to better understand them, we will get started with building a to-do list, specifically focusing on the useState Hook.

Note: It is assumed you already know at least the basics of React. If you’re new to learning React, that’s okay. Check out our React tutorial for beginners before continuing here.

Overview of React Hooks#

In React, Hooks are functions that allow you to hook into React state and lifecycle features from function components. This allows you to use React without classes.

When you take an initial look at the React Hooks documentation, you’ll see that there are several Hooks that we can use for our applications. You can even create your own. Some of the popular ones include:

  • useState: Adds state to functional components.

  • useEffect: Performs side effects in function components.

  • useContext: Accepts a context object and returns the current context value.

  • useCallback: Returns a memoized callback.

Benefits of using Hooks#

  • Isolating stateful logic, making it easier to test

  • Sharing stateful logic without rendering props or higher-order components

  • Separating your app’s concerns based on logic

  • Avoiding ES6 classes

The only Hook we will need for this particular to-do list project is the useState Hook. This Hook replaces the need for a state object in a class component structure.

Class component in React #

When looking at older React legacy code, most likely, you will see something like the following:

The class component structure describes an instance of an App object that has a state that is an array of movies. We render that array of movies by mapping over the state object and returning every movie within its own <div> element.

Functional component in React #

Stateful functional components are very similar in that they hold state, but they are much simpler. Take the following example:

The useState Hook is deconstructed into an array with two items in it:

  • The variable that holds our state (movies)

  • A method that is used to update that state if you need to (setMovies)

The useState Hook not only creates and initializes a local state movies within the App component, but also ensures that the App component will re-render whenever setMovies is called. This way, any changes to movies will immediately appear in the component’s output.

Now that you have the basic idea behind the useState React Hook, let’s see how to use it when creating a To-Do list application.

React To-Do List: Project Prompt#

Our goal is to create a To-Do List UI with the following components:

  1. Header: Labels the To-Do List.

  2. To-Do Items List: Displays each to-do item. We will also create two additional capabilities for the list:

    1. Ability to strikethrough a task that indicates completion.

    2. Remove all completed tasks from the list with a button click.

  3. Form: Adds new to-do task item to the list.

A sample frontend of the To-Do List app
A sample frontend of the To-Do List app

Let's start with a step-by-step guide on how to create a To-Do list in React using the useState Hook.

Step 1: Create a React application#

The first step is to create a React application. You can use either Yarn or npm to set up your React project:

# Using Yarn
yarn create react-app todo-list
# Using npm
npx create-react-app todo-list
Create a React application

Navigate into the todo-list folder and start the application. Your project should now be running on http://localhost:3000.

cd todo-list
yarn start
# or
npm start
Start the application

Step 2: Set up the App component#

Navigate to the App.js file and clear the existing content inside the <div> tags. We won’t need any of the pre-populated code. The App.js file should look something like this:

Step 3: Create the Header component#

A header enhances the application's UI by clearly indicating its purpose. We'll create a reusable Header component.

Create a new file, Header.js, in the src directory. Inside this file, create a functional component, Header, that returns JSX to render the header section. The JSX should display a header identifying the name of your application. Finally, export your Header component and import it to App.js.

Step 4: Add mock data#

To simulate real-world data and test our application, we’ll create a mock dataset representing to-do tasks.

For this, create a file data.json, in the src directory. Populate the data.json file with sample tasks using the JSON data given below.

[
{ "id": 1, "task": "Give dog a bath", "complete": true },
{ "id": 2, "task": "Do laundry", "complete": true },
{ "id": 3, "task": "Vacuum floor", "complete": false },
{ "id": 4, "task": "Feed cat", "complete": true },
{ "id": 5, "task": "Change light bulbs", "complete": false },
{ "id": 6, "task": "Go to Store", "complete": true },
{ "id": 7, "task": "Fill gas tank", "complete": true },
{ "id": 8, "task": "Change linens", "complete": false },
{ "id": 9, "task": "Rake leaves", "complete": true },
{ "id": 10, "task": "Bake Cookies", "complete": false },
{ "id": 11, "task": "Take nap", "complete": true },
{ "id": 12, "task": "Read book", "complete": true },
{ "id": 13, "task": "Exercise", "complete": false },
{ "id": 14, "task": "Give dog a bath", "complete": false },
{ "id": 15, "task": "Do laundry", "complete": false },
{ "id": 16, "task": "Vacuum floor", "complete": false },
{ "id": 17, "task": "Feed cat", "complete": true },
{ "id": 18, "task": "Change light bulbs", "complete": false },
{ "id": 19, "task": "Go to Store", "complete": false },
{ "id": 20, "task": "Fill gas tank", "complete": false }
]

Each task is an object with id, task, and complete properties.

  • id: It is a unique identifier for each task, crucial for React’s list rendering.

  • task: It is the description of the to-do item.

  • complete: It is a boolean indicating whether the task is completed.

You also need to import the mock data from the data.json file in the App.js file.

Step 5: Read the list of to-do items and display them#

Now that we have our data and header set up, we’ll create components to display the list of tasks. The first thing to do here is to initialize a new state in the App component using the useState Hook. This state will store our mock data for the component.

Basic syntax for the useState() Hook#
import { useState } from 'react'; // import the Hook
const [ variable, setVariable ] = useState(initialState);
Syntax for useState hook

We will create a new state, toDoList, and initialize it with the imported mock data. It will hold the current list of tasks and setToDoList will be used to update this state.

Now we need to map over the toDoList state to render each to-do item in our application. We'll create two components to display the list of tasks.

Create two new files, ToDoList.js and ToDo.js in the src directory. The ToDoList.js file will have a component—ToDoList—that will serve as the container that holds all of our todos, and ToDo.js file will have a component—ToDo— that will render an individual to-do item in our To-do list.

Let's first look at the ToDoList.js file.

We have the ToDoList component that receives toDoList as a prop from the parent component—App.js. It then iterates over the toDoList array and renders a ToDo component for each task. It also assigns a unique key to each ToDo component using the task's id to help React optimize rendering.

Let's first look at the ToDo.js file.

We have the ToDo component that receives a single todo object as a prop. It then renders the task property of the todo object.

Now that both the components are set, we need to import and use the ToDoList component in the App.js file.

Run the code above, and you should now see the "To-Do List" header followed by a list of tasks, each displayed within its own row.

The Road to React: The One with Hooks

Cover
The Road to React: The One with Hooks

This is a relaunch of my existing course, The Road to Learn React. A lot has changed in React since I first created this course, and so here I am to give you all the information you need to work with modern React. (If you’re looking for content on legacy React, the old course is still available as well.) In this course you will take a deep dive into React fundamentals, covering all new React concepts including Hooks. I do address some legacy features in case you’re working with an older codebase, but the majority of this course will focus on working with modern React. You will learn how to style your app, techniques for maintaining your app, and some more advanced concepts like performance optimization. Throughout the course, you will gain hands-on experience by building a Hacker News app, and by the end of this course, you will be prepared to build your own applications and have something to showcase in your portfolio.

25hrs
Beginner
74 Playgrounds
13 Quizzes

Step 6: Toggle task completion#

We'll allow users to mark tasks as complete or incomplete by clicking on them. Completed tasks will be visually distinguished with a strikethrough.

We will update the ToDo.js file to conditionally apply a CSS class based on the task's completion status. We add the attribute className that conditionally apply the strike class if todo.complete is true, otherwise applies no class.

const ToDo = ({todo}) => {
return (
<div className={todo.complete ? "strike" : ""}>
{todo.task}
</div>
);
};

Note: Anything in between curly braces when using JSX signals that we are using JavaScript functions.

In the styles.css file, we add the strike class which applies a line-through to indicate completion and changes the text color to gray for better visibility.

.strike {
text-decoration: line-through;
color: gray;
}

If you were to look at your React application, you would see some of the tasks with a line through them indicating that a project or task has been completed.

Next, we have to create a function that will toggle the complete status of a task. We will implement this function in the App.js file since our state resides there.

Creating a toggle function (toggle()) is fairly simple. What we want to do is that when a user clicks on a task, we want to change the state of complete to true if it’s false or vice versa. We will use the second variable in our deconstructed useState array to do this.

const handleToggle = (id) => {
let mapped = toDoList.map(task => {
return task.id == id ? { ...task, complete: !task.complete } : { ...task};
});
setToDoList(mapped);
}

For this function, I passed in the id of the item that was clicked. Mapping over the toDoList creates a new array. We can find the id of the current target and then flip the task to complete or not complete depending on the Boolean already passed in.

setToDoList(mapped) is analogous to this.setState({ toDoList: mapped }), which was used when we worked with state in class components.

You can now toggle on and off tasks that are completed!

Delete completed tasks#

What are we going to do with all of those crossed-off, completed tasks? Let’s delete them! Create a button that will have an onClick handler that filters out all of the completed items.

This is super similar to the handleToggle function we just did. All we need to do is take the toDoList and filter through it, return all items that are not completed, and then set the filtered array onto toDoList.

Because the filter method returns a new array, we are not in danger of mutating state and can proceed without making a copy of the array before we play with it.

const handleFilter = () => {
let filtered = toDoList.filter(task => {
return !task.complete;
});
setToDoList(filtered);
}

Then, add a button to the end of the ToDoList component and set an onClick to fire the handleFilter function. You want to be certain to add your handleFilter function to App.js and then pass down the function as props to the ToDoList.

import React from 'react';
import ToDo from './ToDo';
const ToDoList = ({toDoList, handleToggle, handleFilter}) => {
return (
<div>
{toDoList.map(todo => {
return (
<ToDo todo={todo} handleToggle={handleToggle} handleFilter={handleFilter}/>
)
})}
<button style={{margin: '20px'}} onClick={handleFilter}>Clear Completed</button>
</div>
);
};
export default ToDoList;

8. Add tasks with a form component#

The final item on our list is to create a form component that will handle adding tasks to our ToDoList. Create a new file in your src directory and call it ToDoForm.js.

Create a basic form that will allow a user to input a task name, hit enter or click on a button, and have a function fire to add the task. For a form to work correctly we have to keep track of the changes as we go, so logically we have to handle what happens as the input changes.

Form Logic

There are four main things that we need to have to make our forms work:

  • Local state (so we will need to employ the useState() Hook)
  • Our form component with an input value that is assigned to the correct variable
  • A function that handles the state’s changes
  • A function to handle the form submission

useState to handle user input

Add an import for the useState Hook to your React import. Our state here will keep track of any input that the user types into their form. The initial state is set to an empty string since there should be nothing in the form yet.

const [ userInput, setUserInput ] = useState('');

Form Component

Now, create a form component that encapsulates an input and a button. Fairly basic. You can play with style later.

Input.value

Your <input> element should have a value associated with it that matches the name of your state variable (I named mine userInput). The change handler will take the value here and set the state every time it changes.

<input value={userInput} type="text" onChange={handleChange} placeholder="Enter task..."/>

handleChange

This is the function that will handle the local state’s changes. Every time a user types in the input box, the state will change to reflect the most recent input.

const handleChange = (e) => {
       setUserInput(e.currentTarget.value)
   }

handleSubmit

When a user hits ‘Enter’ or clicks the ‘Submit’ button, this function will fire to add the task to the toDoList array.

const handleSubmit = (e) => {
       e.preventDefault();
       addTask(userInput);
       setUserInput(“”);
 
   }

When we use forms, remember to use e.preventDefault() because we don’t want the default action to take place. In this case, it would reload the page and everything changed will go back to how it initially rendered.

Be sure to set userInput back to an empty string after the addTask function has run. This will set the form back to an empty input.

addTask Next is the addTask function. This function goes in App.js since that is where all of our toDoList state is. We need to be able to set the new array on state using setToDoList and we can only do that when the addTask function has access to that state.

 const addTask = (userInput) => {
   let copy = [...toDoList];
   copy = [...copy, { id: toDoList.length + 1, task: userInput, complete: false }];
   setToDoList(copy);
 }

This function takes in userInput that we gathered from our form component’s current state. Make a copy of the toDoList so we don’t directly manipulate the state.

Next, reassign copy to a new array, with copy spread in, and the new list item tagged on the end. Another way this could be written is:

copy.push({id: toDoList.length + 1, task: userInput, complete: false });

Make sure you pass addTask as props down to the ToDoForm.

Complete To Do List code#

Check out the complete code I used in this application here

The Road to React: The One with Hooks

Cover
The Road to React: The One with Hooks

This is a relaunch of my existing course, The Road to Learn React. A lot has changed in React since I first created this course, and so here I am to give you all the information you need to work with modern React. (If you’re looking for content on legacy React, the old course is still available as well.) In this course you will take a deep dive into React fundamentals, covering all new React concepts including Hooks. I do address some legacy features in case you’re working with an older codebase, but the majority of this course will focus on working with modern React. You will learn how to style your app, techniques for maintaining your app, and some more advanced concepts like performance optimization. Throughout the course, you will gain hands-on experience by building a Hacker News app, and by the end of this course, you will be prepared to build your own applications and have something to showcase in your portfolio.

25hrs
Beginner
74 Playgrounds
13 Quizzes

What to learn next#

Congrats! You’ve now made a to-do list using React Hooks. If you found this to be fairly straightforward, play around with the code a bit and try to implement more functionality.

Here are some extra things you can do to give you some ideas:

  • Add the ability to create a due date for each task or a priority rating
  • Give the ability to sort the list by the due date or priority
  • Create a backend so your To Do List can persist
  • Create a frontend interface for your own custom React app
  • Style application using React-Bootstrap or CSS-in-JS
  • Employ the Context API by using the useContext Hook instead of local state and props

If you want to get more hands-on practice, check out Educative’s course The Road to React: The one with Hooks. This course offers a deep dive into React fundamentals, covering all new React concepts including Hooks. You will gain hands-on experience by building a Hacker News app! This news application is just one of the many use cases for custom React Hooks!

Happy learning!


Continue reading about React features and React functions#

Frequently Asked Questions

What are the components of the Todo list?

The App, TodoList, and TodoItem components collaboratively form a functional Todo List application in React. The TodoList component manages the state of the tasks, while the TodoItem component represents and handles each task within the list.


  

Free Resources