In this tutorial we’ll be creating a Todo list using React. It won’t be the most good looking or most exciting app you have seen. But it is an extremely useful excercise for learning any new language as a beginner, including React.
We will be covering some core concepts of React, such as how forms are used in React and how to properly update state
. The steps that we will follow in this tutorial are:
- Creating a new React app with
create-react-app
. - Creating a static Todo list with mockup data.
- Learn how to create a form in React.
- Refactor and rearrange code to get better data flow.
- Adding functionality for adding a todo to the list.
- Adding functionality for removing a completed todo from the list.
Before we start
It is recommended to have some basic knowledge of JavaScript, React and how to use the terminal. If you’re completely new to React, I recommend that you check our Beginners tutorial on how to get started with React. It is a two part series that will help you with the required setup and give an overview over the React ecosystem.
Getting started
We will be crearting a very basic Todo list app. A user is able to add a task to the list and delete a completed task. The focus in this tutorial is the functionality of the app and we will therefore not add any .css
styling.
You will be guided through the development process step-by-step and it is recommended that you follow by typing the code to get the most out of the tutorial. It might seem unnecessary, but it’s actually a great way of learning.
We’ll start by creating our new React app using create-react-app
. Open the terminal and run the following command one directory above where you want the project to live.
npx create-react-app todo-app
Once it’s finished loading, you can start your local development server and go to localhost:3000.
npm start
You should now see the app up and running with the default app generated by create-react-app
.
Let’s do some cleaning up and structuring of our project. Go to src/app.js
and remove everything within <div className="app">...</div>
and instead add a h1
title. Remove the import "./App.css""
as well.
import React from "react";
function App() {
return (
<div className="App">
<h1>Todo list</h1>
</div>
);
}
export default App;
Starting with a mock
We will start by creating a mock, which in our case is a static list of todos. With our mock, we can create the UI interface of our Todo list.
Under the src/
directory, add a new folder components
which we are going to store our custom built components. We will start by adding our first component, which will be the UI for the todos. Create a new file called todoList.js
. Inside the file, we create our TodoList
component.
We start by creating our mockup, our static list of some example todos that we can use for now. We create a variable todos
and assign an array of strings to it.
const todos = ["Finish homework", "Wash dishes", "Clean room", "Make waffles"];
Then we create our component TodoList
which we create as a functional component that returns an unordred list <ul>
.
const TodoList = () => <ul></ul>;
A neat feature with JSX is that we can embed JavaScript expressions inside the unordered list. Inside the unordered list we call the map()
function on our array of todos. The map()
function returns a new array with a listitem
for every todo in the todos list.
<ul>
{todos.map(todo => (
<li key={todo}>{todo}</li>
))}
</ul>
Remember to import React
as it is required when we are using JSX which will be transpiled to use the function React.createElement()
.
The todoList.js
file should now look like this.
import React from "react";
const todos = ["Finish homework", "Wash dishes", "Clean room", "Make waffles"];
const TodoList = () => (
<ul>
{todos.map(task => (
<li key={task}>{task}</li>
))}
</ul>
);
export default TodoList;
Separting components
Before we move on, there’s one thing to point out here. At the moment we don’t have so much code but our project might grow in complexity. It is a good idea to create a seperate component for the items in the Todo list, as they actually are seperate components.
import React from "react";
const todos = ["Finish homework", "Wash dishes", "Clean room", "Make waffles"];
const Todo = props => <li>{props.todo}</li>;
const TodoList = () => (
<ul>
{todos.map(todo => (
<Todo todo={todo} key={todo} /> ))}
</ul>
);
export default TodoList;
The Todo
component basically returns a list item with the todo passed down as a prop
from the map()
function in the TodoList
component. Note how we keep the key attribute at the same level as before.
You might have noticed that we don’t export the Todo
component. Since we’re only using it locally within the TodoList.js
file, there is no need to export it.
Now that we have our component created and have some mockup data, we need to import and add it to our app
component.
import React from "react";
import "./App.css";
import TodoList from "./components/todoList";
function App() {
return (
<div className="App">
<h1>Todos</h1>
<TodoList /> </div>
);
}
export default App;
We should now be able to see our todoList
in our app.
The Todo form
Now with our Todo list setup, we need a way to add new todos to the list. We will start by adding a new component TodoForm
in a seperate file within the components
folder. The TodoForm
will have a input field and a submit button.
We create a new file TodoForm.js
. Inside we add a new class component which returns a form with a text input
and a button for adding the todo to the list. It’s usually recommended to start with a functional component, but you will soon see why we need to create a class component.
import React from "react";
class TodoForm extends React.Component {
render() {
return (
<form>
<input type="text" placeholder="Add todo" />
<input type="submit" value="Submit" />
</form>
);
}
}
export default TodoForm;
The HTML form
element behaves a bit differently from other DOM elements in React, because it naturally keeps its own internal state. If we look at our form above, it has a default HTML behavior of browsing to a new page when clicking on the submit button.
If this is the desired functionality, then it works just fine as it is. However, we want to add the entered todo
to our list without be redirected to a new page. The standard way to acheive this functionality is by using controlled components
Forms in React
In order to prevent the default behaviour of the form, we need to add a JavaScript function which handles the submission of the form for us. In order for the function to be able to do so, it needs to have access to the data entered in the form (in our case the todo
).
HTML elements in forms such as the <input>
field typically maintain their own state which is updated based on user input. React on the other hand keeps state in the state property of the component and is only updated with the setState()
function.
By combining these two, the React component which renders the form also controls what happens on user input. The <input>
form element is thereby controlled by React and is therefore called a controlled component.
Creating the form
There are some few steps we need to go through in order to create the form for adding a new todo
.
- Create a class component (it needs a local state).
- Add a class constructor to assign the initial value of the local state.
- Add a function that updates the local state on user input.
- Add a function which handles the submit.
Step 1. We’ve already covered the first step, so lets move on to step 2.
Step 2. We create the constructor and remember to call the function super(props)
. Then we assign state the initial value of an empty string ""
to the todoInout
.
class TodoForm extends React.Component {
constructor(props) { super(props); this.state = { todoInput: "" }; }
render() {
return (
<form>
<input type="text" placeholder="Add todo" />
<input type="submit" value="Submit" />
</form>
);
}
}
Step 3. We create a function which updates todoInput
in state with the updated value that the user enters in the form. We use an arrow function which is a feature introduced in the JavaScript ES6.
handleChange = event => {
this.setState({ todoInput: event.target.value });
};
Now we need to add so the function gets called when the user updates the input. We also add the value attribute and assigning it the value of the state.
<input
type="text"
placeholder="Add todo"
value={this.state.todoInput} onChange={event => this.handleChange(event)}/>
If we go to localhost:3000, we should be able to see the changes in the React Developer Tools. Open the Components tab in the Chrome Developer Tools and click on the TodoForm
. Down to the right we can se the state with the initial value that we set the state to. If we start typing in the input field we should see the state changing according to what we type.
Try adding a new Todo
by clicking on the Submit
button, what happens?
The page reloads but the Todo
isn’t added. This is because the default behaviour of the HTML form, we have not yet created our custom function for handling the submit.
Step 4. We create a new function handleSubmit()
which should add the new todo to our list. But first, we need to prevent the default behaviour of the HTML form.
handleSubmit = event => {
event.preventDefault();
alert(`A todo was added: ${this.state.todoInput}`);
// Add the new todo to the list
};
Before we actually add the new todo
to the list, we check so it actually prevents the page reload and shows an alert message with the new todo. We also need to add so the function gets called when the user clicks on the submit
button.
render() {
return (
<form onSubmit={event => this.handleSubmit(event)}> <input
type="text"
placeholder="Add todo"
onChange={event => this.handleChange(event)}
/>
<input type="submit" value="Submit" />
</form>
);
}
Lets try adding a new todo and submitting the form.
Now we need to be able to actually the todo
to our list. This requires restructuring in our code, let’s dive in!
Adding todo
Let’s take a moment to think about our components and how they are currently stored. We have two components:
TodoList
- a basic list which shows all thetodos
(green)TodoForm
- a form for adding newtodos
(red)
Currently the list of todos
is stored in Todolist.js
and when we add a new todo
it has be stored in that list. With the current structure, we would be required to first pass the new todo
data up to the app
component and then down to TodoForm
. This might confuse you a bit and structure is not optimal.
To improve the structure and data flow, we will move the todos
list to the app
component instead. At the same time we will give the app
component life by giving it a local state. By now, you should have some familiarity with this process.
Let’s start by changing the app
component to a class component with state. We will assign our mockup data to the initial value of the todos
list.
import React from "react";
import TodoList from "./components/todoList";
import TodoForm from "./components/todoForm";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { todos: ["Finish homework", "Wash dishes", "Clean room", "Make waffles"], }; }
render() {
return (
<div className="App">
<h1>Todos</h1>
<TodoList todos={this.state.todos} /> <TodoForm />
</div>
);
}
}
export default App;
Now that we have the list in app.js
we need to pass it down as a prop
to the TodoList
. Lets add so our function accepts props
as a parametar.
const TodoList = props => (
<ul>
{props.todos.map(todo => (
<Todo todo={todo} key={todo} />
))}
</ul>
);
Open your browser and make sure the app is the same as before.
Now it is time to create a function which adds the todo
from the TodoForm
, to the todos
list. We will be creating a function in the app.js
that takes a todo
as a parameter and calls the setState()
function to add it to the list. Then we will be passing down this function to the TodoForm
which we can use in the handleSubmit()
function.
Do not worry if you have a hard time understanding this process, we will go through it step-by-step. Updating an array in React state can be tricky since we should not mutate the state. But let’s start by creating the function in app.js
.
addTodo = todo => {
this.setState(prevState => ({
todos: [...prevState.todos, todo],
}));
};
The function takes in one parameter todo
which is the new todo
to add to the list. Then we update the state with the setState()
function. But instead of passing in a object, we pass in a function with the current state as parameter.
Then we update todos
in the state with the value of [...prevState.todos, todo]
. This is a ES6 feature called Spread syntax. It basically adds todo
to our current state and then returns a new array.
With our function for adding a new todo
in place, we need to pass down this function as a prop to the TodoForm
.
<TodoForm addTodo={this.addTodo} />
We can then access it through props
in the TodoForm
and call it in the handleSubmit()
function. We can remove the alert()
.
handleSubmit = event => {
event.preventDefault();
this.props.addTodo(this.state.todoInput);
};
Lets try adding a new todo
in our browser.
It works, now we have added Buy food to our todos. Let’s add a function so we can cross off a todo
when it is completed.
Removing todo
Just like when adding a todo
to the list, removing one will also require some steps to handle the data flow.
First thing first, we need a function in app.js
which removes a todo from the list. We create one and use the filter() function. The function returns a new list with all the elements that pass the test we add to the function. In our case, we want to return all the todos
in the list which are not the todo
that we click on.
deleteTodo = value => {
this.setState(prevState => ({
todos: prevState.todos.filter(todo => todo !== value),
}));
};
The function takes one parametar, which is the string value of the todo
. Then we call the function setState()
to update the state. We pass in prevState
as an argument (which is the state from before updating state) and return an arrow function.
Inside, we want to update our todos
list so we type todos:
and assign it the value of the expression to the right, prevState.todos.filter(todo => todo !== value)
. We call the filter()
function on the todos
in the previous state, and return all the todos
from that list that do not match the value that we pass in to deleteTodo(value)
function.
With the function implemented, we need to pass down this function to the TodoList
component.
<TodoList todos={this.state.todos} deleteTodo={this.deleteTodo} />
Inside the TodoList
component we can once again pass down the deleteTodo()
function to the Todo
component.
const TodoList = props => {
return (
<ul>
{props.todos.map(todo => (
<Todo
todo={todo}
key={todo}
deleteTodo={props.deleteTodo} />
))}
</ul>
);
};
Now we can access the function from the Todo
function and we will be using whenever a user clicks on the todo
. So we add an event handler onClick()
which calls the deleteTodo(todo)
function passing in the value of the todo
as parameter.
const Todo = props => (
<li onClick={() => props.deleteTodo(props.todo)}>{props.todo}</li>
);
Let’s go to the browser and try clicking on a completed todo
. We just finished cleaning the room
so we will click on that one.
Wrapping it up
In this tutorial we built a simple Todo list app where a user can add a new todo and delete a completed one. It is not the most advanced or good looking Todo list, but hopefully you have the learned some new concepts of React (which was the focus of this tutorial).
There are of course more features and functionality we can add to our Todo app to improve it. Some suggestions are:
- Styling the app to make it look better.
- Adding validation before adding to the list. Could be checking so it is not empty or if it already exists in the list.
- Clearing the text
input
after adding the newtodo
to the list. - Enable users to see crossed off
todos
.
If something is unclear our you have any questions, feel free to leave a comment below and I’ll get back to you as soon as possible.
Thanks for your time!