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.
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.
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.
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.
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.
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.
Our goal is to create a To-Do List UI with the following components:
Header: Labels the To-Do List.
To-Do Items List: Displays each to-do item. We will also create two additional capabilities for the list:
Ability to strikethrough a task that indicates completion.
Remove all completed tasks from the list with a button click.
Form: Adds new to-do task item to the list.
Let's start with a step-by-step guide on how to create a To-Do list in React using the useState
Hook.
The first step is to create a React application. You can use either Yarn or npm to set up your React project:
# Using Yarnyarn create react-app todo-list# Using npmnpx create-react-app todo-list
Navigate into the todo-list
folder and start the application. Your project should now be running on http://localhost:3000
.
cd todo-listyarn start# ornpm start
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:
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
.
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.
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.
import { useState } from 'react'; // import the Hookconst [ variable, setVariable ] = useState(initialState);
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
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.
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 tothis.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!
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;
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:
useState()
Hook)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 theToDoForm
.
Check out the complete code I used in this application here
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.
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:
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!
Free Resources