Building a Simple Todo List App in React

by kirupa   |   10 October 2016

If creating the Hello, World! example was a celebration of you getting your feet wet with React, creating the quintessential Todo List app is a celebration of you approaching React mastery! In this tutorial, we are going to tie together a lot of the concepts and techniques you've learned to create something that works as follows:

The way this Todo List app works is pretty simple. Type in a task or whatever you want into the text field and press Add (or hit Enter/Return). Once you've submitted your task, you will see it appear as an entry. You can keep adding tasks to add additional entries and have them all show up:

Pretty simple, right? In the following sections, we will build this app from scratch and learn (in awesomely painstaking detail) how things work along the way.

Onwards!

Getting Started

By now, you know the drill. We need a starting point, so go ahead and create a new HTML document. Inside it, add the following content into it:

<!DOCTYPE html>
<html>
 
<head>
  <title>React! React! React!</title>
  <script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
  <script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
 
  <style>
 
  </style>
</head>
 
<body>
 
  <div id="container">
 
  </div>
 
  <script type="text/babel">
    var destination = document.querySelector("#container");
 
    ReactDOM.render(
      <div>
        Hello!
      </div>,
      destination
    );
  </script>
</body>
 
</html>

If you preview all of this in the browser, you will see the words Hello! appear. If you see that, then you are in good shape. It's time to start building our Todo List app!

Creating the UI

Right now, our app doesn't do a whole lot. It doesn't look like much either. We'll deal with the functionality in a little bit, but first let's get the various UI elements up and running. That isn't very complicated for our app! The first thing we are going to do to is get our input field and button to appear. This is all done by using the div, form, input, and button elements!

All of that will live inside a component we are going to call TodoList. Go ahead and add the following code above where you have your ReactDOM.render method:

var TodoList = React.createClass({
  render: function() {
      return (
        <div className="todoListMain">
          <div className="header">
            <form>
              <input placeholder="enter task">
              </input>
              <button type="submit">add</button>
            </form>
          </div>
        </div>
      );
    }
});

Inside your ReactDOM.render method, we need to call our newly added TodoList component to render it. Go ahead and replace your existing JSX with the following:

ReactDOM.render(
  <div>
    <TodoList/>
  </div>,
  destination
);

Save your changes and preview what you have right now in your browser. You'll see something that looks as follows:

If you are surprised at what you see, take a few moments to look at the JSX we defined inside the TodoList component. There shouldn't be anything surprising there. We just defined a handful of HTML elements that look really REALLY boring. Speaking of that, let's make our HTML elements look less boring by introducing them to so some CSS!

Inside your style region, add the following:

body {
  padding: 50px;
  background-color: #66CCFF;
  font-family: sans-serif;
}
.todoListMain .header input {
  padding: 10px;
  font-size: 16px;
  border: 2px solid #FFF;
}
.todoListMain .header button {
  padding: 10px;
  font-size: 16px;
  margin: 10px;
  background-color: #0066FF;
  color: #FFF;
  border: 2px solid #0066FF;
}

.todoListMain .header button:hover {
  background-color: #003399;
  border: 2px solid #003399;
  cursor: pointer;
}     

Once you've added all of this, preview your app now. Because our HTML elements had the appropriate className values set on them, our CSS will kick in and our example will now look as follows:

At this point, our app looks pretty good. It doesn't do much, but at least we are making progress. In the next section, we will start to make our app actually do things.

Creating the Functionality

The actual implementation of our Todo List app functionality is not as crazy as you might think. Let's take a high-level view of how it works. The most important piece of data is the text you enter into the text field. Each time you enter some text and submit the form, that text gets visually displayed in a list below any previous pieces of text you submitted. So far, this makes sense, right?

All of this is done by simply taking advantage of React's state functionality. Inside our state object, we have an array that is responsible for storing everything you enter:

Each time this array of items gets updated with new text that you submit, we update what you see with the newly submitted text. The rest of the work is just around setting up events and event handlers to ensure we can submit the form and know exactly what text to add to our array of items. In the following sections, we are going to turn all of this English we've seen here into React-flavored JavaScript and JSX!

Initializing our State Object

The first thing we are going to do is initialize our state object with the array that will be responsible for storing all of the submitted text. Inside our TodoList component, add the following highlighted lines:

var TodoList = React.createClass({
  getInitialState: function() {
    return {
      items: []
    };
  },
  render: function() {
      return (
        <div className="todoListMain">
          <div className="header">
            <form>
              <input placeholder="enter task">
              </input>
              <button type="submit">add</button>
            </form>
          </div>
        </div>
      );
    }
});   

What we are doing here is specifying the getInitialState lifecycle method that gets called before our component renders. Inside that method, we create an empty array called items that we can then access via this.state.items from anywhere inside this component.

Handling the Form Submit

We add new items to our "todo" list when you submit the form either by pressing the Add button or hitting Enter/Return on your keyboard. This behavior is mostly built-in to HTML and our browsers know all about how to deal with this. We don't have to write any special code for dealing with the Enter/Return key or listening for a press on the Add button. The only thing we need to worry about is dealing with what happens when the form actually gets submitted.

To do that, we listen to the onSubmit event on our form element. This event is fired every time the form is submitted, and that includes hitting the Enter/Return key or fiddling with any element that has a type attribute of submit on it. When the form is submitted and that event gets overheard, we will need to call an event handler. Let's give that event handler a name of addItem.

Putting all of this together, inside your TodoList component's render function, make the following highlighted change:

render: function() {
  return (
    <div className="todoListMain">
      <div className="header">
        <form onSubmit={this.addItem}>
          <input placeholder="enter task">
          </input>
          <button type="submit">add</button>
        </form>
      </div>
    </div>
  );
}     

As we had hoped to do, we just linked our form element's onSubmit event to the addItem event handler. This event handler doesn't exist, but we are going to fix that by adding the following highlighted lines:

var TodoList = React.createClass({
  getInitialState: function() {
    return {
      items: []
    };
  },
  addItem: function(e) {
    
  },
  render: function() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form onSubmit={this.addItem}>
            <input placeholder="enter task">
            </input>
            <button type="submit">add</button>
          </form>
        </div>
      </div>
    );
  }
});   

Our addItem event handler/function doesn't do a whole lot right now, but the important thing is that it exists! Next, we'll fix the part where it doesn't do a whole lot.

Populating Our State

Right now, our TodoList component's state object contains the items array. What we need to do is populate this array with the text that you enter into the input field. That means we need a way to access our input element from within React. The way we are going to do that is by setting a ref attribute on our input element and storing the reference to the HTML element that gets generated.

Inside our TodoList component's render method, add the following line:

render: function() {
    return (
      <div className="todoListMain">
        <div className="header">
        <form onSubmit={this.addItem}>
            <input ref={(a) => this._inputElement = a}
                   placeholder="enter task">
            </input>
            <button type="submit">add</button>
          </form>
        </div>
      </div>
    );
  }

When this highlighted code runs, which is immediately after this component mounts, the _inputElement property will store a reference to the generated input element. Now that we have done this, we can treat this element like we would any DOM element we might have found using querySelector or equivalent function in the non-React world. What we are going to do next is populate our items array!

Go ahead and modify the addItem method by adding the following lines:

addItem: function(e) {
  var itemArray = this.state.items;
  
  itemArray.push(
    {
      text: this._inputElement.value,
      key: Date.now()
    }
  );

  this.setState({
    items: itemArray
  });

  this._inputElement.value = "";

  e.preventDefault();
}

This looks like a lot of code you just added, but all we are doing here is putting into JavaScript our earlier stated goal of populating our items array with text from our input field. Let's walk through this code in greater detail.

The first thing we do is create an array called itemArray that stores a reference to our state object's items property:

var itemArray = this.state.items;

Once we have this array, we add to it our recently submitted text entry from our input element:

itemArray.push(
  {
    text: this._inputElement.value,
    key: Date.now()
  }
);    

Notice that we aren't just adding the text entry from our input element. We are instead adding an object made up of the text and key properties. The text property stores our input element's text value. The key property stores the current time. This sounds like a bizarre thing to do, but as you recall from the Going from Data to UI article, the goal is to have this key value be unique for every entry that gets submitted. This is important because (spoiler alert!) we will be using the data in this array to eventually generate some UI elements. This key value is what React will use to uniquely identify each generated UI element, so by generating the key using Date.now(), we ensure a certain level of uniqueness. Because this is an important (yet easy to overlook) detail, we will revisit all of this again in a few moments.

Anyway, getting back on track, once we are done with the itemArray, all that remains is to set our state object's items property to it:

this.setState({
  items: itemArray
});   

Almost done here! The last thing we do in this method is the following:

e.preventDefault();

The preventDefault method ensures we override the default onSubmit event. The reason we do this is a bit obscure, but it is to ensure the following: all we want to do when we submit the form is call the addItem method. If we didn't stop the default behavior, our app will correctly call addItem as desired when we submit the form. It will also trigger our browser's default POST behavior - which we definitely don't want. By stopping the onSubmit event from performing the default behavior, we get our desired behavior of calling the addItem method without any of the unwanted side effects like an unnecessary POST action that might refresh your page.

Displaying the Tasks

We are almost done here! The last-ish thing we are going to do is visualize the tasks that currently live inside our state object's items array. This is going to involve creating a whole new component called TodoItems, passing around some props, using the map function, and doing other awesome andrenaline-inducing things:

Anyway, the first thing we are going to do is define our TodoItems component. In your code, just above where you have the TodoList component defined, go ahead and add the following in:

var TodoItems = React.createClass({
  render: function() {

  }
});   

There is nothing going on right now, but that's OK.

Next, what we are going to do is call this component from inside the TodoList component's render method. Not only that, we are going to specify a prop and pass in our TodoList component's state object that contains our items array. Doing all of this is really simple, so go ahead and add the following highlighted line to your TodoList component's render method:

render: function() {
  return (
    <div className="todoListMain">
      <div className="header">
        <form onSubmit={this.addItem}>
          <input ref={(a) => this._inputElement = a}
                 placeholder="enter task">
          </input>
          <button type="submit">add</button>
        </form>
      </div>
      <TodoItems entries={this.state.items}/>
    </div>
  );
}   

All we did here is instantiate our TodoItems component and pass in our items state property to a prop called entries. At this point, if you run our app in the browser, nothing visible will happen. Our TodoItems component is ready to render, and it has access to all of the tasks that were submitted. The only problem is that it doesn't really do anything with all of that, but we are going to fix that up next.

Getting back to our TodoItems component, the first thing we are going to do is create a new variable to store our passed in array of tasks. To do that, add the following highlighted line:

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries;

  }
});

We just added a variable called todoEntries, and it stores the value from the entries prop that we passed in based on the TodoList component's this.state.items value. Sweet! Now, our todoEntries variable stores an array containing a bunch of objects that each store a task and a key. All that remains is to create the HTML elements that will be used to display our data.

In the first step towards accomplishing that, add the following highlighted lines of code to create the li elements:

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = todoEntries.map(createTasks);
  }
});

We are using the map function to iterate over every item inside todoEntries and call the createTasks function to create a list element for each entry:

function createTasks(item) {
  return <li key={item.key}>{item.text}</li>
}     

To reiterate a point we made earlier, since these list elements are dynamically created, we need to help React keep track of them by specifying the key attribute and giving each a unique value. We already solved this part of the problem when we stored our tasks initially as you recall:

itemArray.push(
  {
    text: this._inputElement.value,
    key: Date.now()
  }
);    

Because of our earlier planning, we take the easy street right now by assigning our key attribute the item.key value that each item in our todoEntries array already contains. Our list element's visible content is simply the text value stored by item.text. There is no extra explanation needed for how we use that one. Quite refreshing, isn't it?

Putting all of this together, this collection of list elements is fully processed and stored by our listItems variable/array. All that remains at this point is to go from list elements inside an array to list elements rendered on the screen. To accomplish that, go ahead and add the following highlighted lines:

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = todoEntries.map(createTasks);

    return (
      <ul className="theList">
        {listItems}
      </ul>
    );
  }
});   

What we are doing is returning an ul element whose contents are the list elements stored by listItems. After you've added this, save your document and preview your app. You'll see something that looks like the following after entering a few tasks:

Our app works! Every task you submit shows up in its own list item. Take a few deep breaths and relax for a few moments. This is awesome progress, and all we have left are a few little things here and there that need to be wrapped up.

Adding the Finishing Touches

We are almost done here! First, what we have right now doesn't look exactly like the example we started out with. Our list of tasks looks a bit plain, but that can be fixed with some CSS magic. Inside your style block, add the following style rules just below where your existing style rules live:

.todoListMain .theList {
  list-style: none;
  padding-left: 0;
  width: 255px;
}

.todoListMain .theList li {
  color: #333;
  background-color: rgba(255,255,255,.5);
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 5px;
}     

If you preview your app now, you'll see that the entered tasks look exactly as you expected them to:

Next, have you noticed that whatever you enter into the input field doesn't go away after you submit the form? You have to manually clear out the field each time after submitting a task...like an animal! That is annoying, but the fix for it is quite simple. Inside our TodoList component's addItem method, add the following highlighted line:

addItem: function(e) {
  var itemArray = this.state.items;

  itemArray.push(
    {
      text: this._inputElement.value,
      key: Date.now()
    }
  );

  this.setState({
    items: itemArray
  });

  this._inputElement.value = "";

  e.preventDefault();
}

All we are doing here is clearing our input element's value property when the form is submitted and the addItem method gets called. This ensures that we no longer have to manually clear out our input field between each task we would like to submit. Simple bimple!

Conclusion

Our Todo app is pretty simple in what it does, but by building it from scratch, we covered almost every little interesting detail React brings to the table. More importantly, we created an example that shows how the various concepts we learned individually play together. That is actually the important detail. Now, here is a quick question for you: does everything we've done in this tutorial make sense?

If everything we've done in this tutorial makes sense then you are in good shape to tell your friends and family that you are close to mastering React! If there are areas that you find confusing, I suggest you go back and re-read the article(s) that addresses your confusion. If you still are stuck, feel free to leave a comment below and I'll be happy to personally help you out.

If you have a question about this or any other topic, the easiest thing is to drop by our forums where a bunch of the friendliest people you'll ever run into will be happy to help you out!

THE KIRUPA NEWSLETTER

Get cool tips, tricks, selfies, and more...personally hand-delivered to your inbox!

( View past issues for an idea of what you've been missing out on all this time! )

WHAT DO YOU THINK?

NEWSLETTER

No spam. No fluff. Just awesome content sent straight to your inbox!

Awesome and high-performance web hosting!
BACK TO TOP
new books - yay!!!