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:

  1. Creating a new React app with create-react-app.
  2. Creating a static Todo list with mockup data.
  3. Learn how to create a form in React.
  4. Refactor and rearrange code to get better data flow.
  5. Adding functionality for adding a todo to the list.
  6. 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.

New 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>
Note: The "key" attribute we have added to the listitem is required when creating lists of elements. They help React identify which items have been chanegd, added or removed. Therefore it needs to uniquely identify each element.

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 propfrom 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.

Added todoList component

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.

  1. Create a class component (it needs a local state).
  2. Add a class constructor to assign the initial value of the local state.
  3. Add a function that updates the local state on user input.
  4. 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.

State updates by form input

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.

Submitting React 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:

  1. TodoList - a basic list which shows all the todos (green)
  2. TodoForm - a form for adding new todos (red)

Overview of project

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 tododata 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 todoto 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.

Adding a new todo

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.

Deleting todo from list

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:

  1. Styling the app to make it look better.
  2. Adding validation before adding to the list. Could be checking so it is not empty or if it already exists in the list.
  3. Clearing the text input after adding the new todo to the list.
  4. 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!