If creating the Hello, World! example was a celebration of getting your feet wet with React, creating the quintessential Todo List app is a celebration of approaching React mastery. In this chapter, we tie together a lot of the concepts and techniques you’ve learned to create something that works as follows: https://www.kirupa.com/react/examples/todo.htm.
You start with a blank app that allows you to enter tasks for later (see Figure 15.1).
The way this Todo List app works is pretty simple. You type in a task or item or whatever you want into the input field and then press Add (or click Enter/Return). After you’ve submitted your item, you’ll see it appear as an entry. You can keep adding items and have them all show up (see Figure 15.2).
To remove an item, just click on an existing entry. Pretty simple, right? In the following sections, we build this app together from scratch using a lot of the adrenaline-inducing techniques you’ve learned so far.
This is going to be a fun exercise of building each part of the app and learning (in awesomely painstaking detail) how the various little things work along the way.
The first step is to create a new React app, as you learned in Chapter 13, “Setting Up Your React Dev Environment Easily.” From your command line, navigate to the folder where you want to create your new project and enter the following:
Press Enter/Return to run that command. A few moments later, a brand new React project is created. We want to start from a blank slate, so we’re going to delete everything contained in our
public folder and our
By now, you know the drill. You need a starting point, so go ahead and create a new HTML document inside your
public folder called
index.html. Inside it, add the following content:
This page is pretty basic, as you can tell. The real magic is going to be happening in the
src directory, create a new file called
index.css and add the following style rules into it:
src directory, add a new file called
index.js. Inside this file, add the following content:
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. First we’ll get our input field and button to appear. This is all done by using the
All of that will live inside a component we’ll call
TodoList. In your
src folder, add a file called
TodoList.js. Inside this file, add the following:
Take a moment to glance at what you’ve added. You can see a bunch of JSX that gets the form elements up and running. To use the newly created
TodoList component, let’s go back to
index.js and reference it to see how our app looks now. Go ahead and make the following two changes:
Save all your changes and preview in your browser. If everything worked, you’ll see something that looks like Figure 15.3.
Right now, we have our input field and submit button showing up. These two UI elements neither work nor look too visually appealing. We’ll fix that in a little bit, but first let’s talk about how we’re going to add the rest of the app’s functionality.
As you can imagine, getting the initial UI elements to show up is the easy part. Tying up all the visuals with the underlying data is where the real work lies. This work can roughly be divided into five parts:
1. Adding items
2. Displaying items
4. Removing items
5. Animating items as they are added or removed
Individually, all of these little implementation details are easy to wrap your brain around. When you put them together, you need to watch out for a few things. We’ll look at all that and more in the following sections.
The first major task to tackle is setting up the event handlers and default form-handling behavior to allow us to add an item. Go back to the
form element and make the following highlighted change:
We listen for the
submit event on the form itself, and we call the
addItem method when that event is overheard. Notice that we aren’t listening for any event on the button itself. This is because our button has a
type attribute set to
submit. This is one of those HTML trickeries in which clicking on the button whose
submit is the equivalent of firing the
submit event on the form.
Now it’s time to create our
addItem event handler that will get called when our form gets submitted. Add the following highlighted lines just above where we have our
render function defined:
All we did was define our
addItem event handler and ensure that the keyword resolves properly. We still haven’t done anything remotely close to actually adding a task, so let’s start by first defining our
state object in the constructor:
state object isn’t very complicated. We’re just defining an items array/property that will be responsible for storing the various items that you can enter. All that’s left to do now is read the entered value from our input element and store it in our
items array when the user submits it. The only complication here is actually reading the value from a DOM element. As you know, React puts up a gate between us and the DOM. It doesn’t like to have us accessing DOM elements and fiddling with properties on them, but it does give us a loophole via refs that we can use.
render function, make the following highlighted change:
Here we’re storing a reference to our
input element in the appropriately named
_inputElement property. To state this differently, anywhere inside this component where we want to access our
input element, we can do so by accessing
_inputElement. Now it’s time to fill out our
addItem function with the following content:
Take a moment to look through what we’re doing. We create a variable called
itemArray to store the current value of our items
state object. Next, we check to see if our input element has any content inside it. If it’s empty, we don’t do anything. If our input element has some text entered, we add that text to our
We aren’t just adding the entered text. We’re actually adding an object that contains both the entered text and a unique key value that’s set by the current time (
Date.now()). If you aren’t clear on why we’re specifying the key, that’s okay. You’ll totally see why in a few moments.
The rest of our code is pretty boring. We’re setting our state’s
items property to the value of
itemArray. We’re clearing the value of our
input element to make room for the next todo item. This line here might be less boring:
We’re overriding this event’s default behavior. The reason has to do with how form submission works. By default, when you submit a form, the page reloads and clears everything out. We definitely don’t want that. By calling
preventDefault, we block the default behavior. That’s a good thing!
It’s time to take stock of where we are right now. If you preview your app and check the browser console, you’ll see our
state object correctly populating with each new todo item we added (see Figure 15.4).
For an alternate approach for setting new state inside the add. This method without modifying the existing state, check out this version: http://bit.ly/setStateConcat.
Having our todo items show up only in the console might be exciting for some of your users, but I’m pretty certain that most probably want to see these items displayed directly on the page. To do this, we’re going to rely on another component. To get started, let’s call this component
TodoItems, specify it in our
render method, and pass in our
items array as a prop.
All of that translated into markup and code looks as follows:
After you’ve done this, add the
import statement to the top of the document as well:
These two changes wrap up the work we want to do in
TodoList.js for now. Next, let’s go ahead and actually create our
TodoItems component. In the
src directory, create a new file called
TodoItems.js and add the following content into it:
This might look like a lot of code to add in one giant swoop, but take a moment to look at what exactly you’re adding. In our
render function, we’re taking the list of todo items (passed in as entries) and turning them into JSX/HTML-ish elements. We do that by calling
map on our items and relying on the
The value stored by our
listItems variable is an array of
li elements that contain the appropriate content to print. Notice that we’re setting the
key attribute—whose value, as you recall, we set earlier using
Date.now()—on each element, to make it easier for React to keep track of the elements.
We turn this list of elements into something we can show onscreen with the following:
After you’ve made this change, save all the changes and preview the app in its current state (
npm start if it isn’t already running). If everything worked properly, not only will you be able to add items, but you also will be able to see them (see Figure 15.5).
If what you see looks similar to the figure, that’s awesome! To celebrate, let’s take a little break from looking at JS and JSX.
Right now, our app’s awesome functionality isn’t reflected in how the app currently looks. We’re going to fix this easily by adding one stylesheet and putting all the relevant style rules into it. In the
src folder, create a new stylesheet called
TodoList.css and add the following style rules into it:
After you’ve created this stylesheet, you need to reference it. In
TodoList.js, add a reference to this stylesheet at the top:
If you preview your app after this change, it will look as shown in Figure 15.6.
As you can see, our app looks much nicer. All we did is add some CSS, so from a functionality point of view, nothing has changed. We’ll make more progress on functionality next.
At this point, we can add items and see them appear. What we can’t do is remove items after they’ve been added. We’re going to allow users to remove items by clicking on them directly. This seems straightforward to implement, right? The only thing to watch out for involves where to put all our code. The items we click on are defined in
TodoItems.js. The actual logic for populating the items lives in our
state object in
TodoList.js. To give you a preview of what to expect, we will be partaking in some shenanigans as we pass things between both of those components.
First we need to set up the event handler for dealing with the
click event. Change the
return statement under
createTasks to look as follows:
We’re simply listening to the
click event and associating it with an event handler called
delete. What might be new is our approach for passing arguments to the event handler. Because of how event arguments and event handlers deal with scope, we work around all those issues using an
After you’ve made this change, you need to define the
delete event handler. Make the following highlighted changes:
Here we define a function called
delete that takes our argument for the item key. To ensure that this resolves properly, we explicitly bind this in the constructor. Notice that our
delete function doesn’t actually do any deleting. It just calls another
delete function passed into this component via props. We’ll work backward from here and deal with that next.
TodoList.js, take a look at our
render function. When calling
TodoItems, let’s specify a prop called
delete and set it to the value of a function called
This change ensures that our
TodoItems component now has knowledge of a prop called
delete. This also means that our
delete function we added in
TodoList actually connects. All that remains is actually defining our
deleteItem function so that it can deal with deleting an item.
First, go ahead and add the
deleteItem function to your
You can add it anywhere, but my preference is to put it just below where our
addItem function lives. Take a look at what this code does. We are passing the key from our clicked item all the way here, and we check this key against all the items we’re storing currently via the
The result of running this code is simple. We create a new array called
filteredItems that contains everything except the item we are removing. This filtered array is then set as our new
items property on our
Our UI then updates and the removed item disappears forever. The last thing we need to do is deal with the usual shenanigans surrounding this. Make the following change in the constructor:
This ensures that all references to this inside
deleteItem will reference the correct thing. Now we have just one more thing to do before we can declare victory in deleting items. Open
TodoList.css and make the following highlighted change and style rule addition:
This provides the hover effect when you move the mouse cursor over the item that you want to remove. With this change done, our functionality to remove an item should be complete. Preview your app now and try adding some items and removing them. It should work well. And now on to just one more thing….
Our very last task is to add some animations, to make adding and removing items look more natural. React offers many ways to animate something. You can use traditional approaches such as CSS animations, CSS transitions,
requestAnimationFrame, the Web Animations API, or even a popular animation library. All of these approaches will take you far…very far.
When it comes to animating the existence of an element, though, the traditional approaches we outlined run into some limitations. This is because React entirely handles the lifecycle of an element as it is about to be deleted from the DOM. We can definitely override some of the lifecycle methods to intercept an element deletion and interject our own animation logic, but that gets us a bit too far into the weeds. We don’t want to deal with that right now.
Fortunately, the React community has come up with a handful of lightweight animation libraries that make animating adding and deleting elements really easy. One such library is Flip Move. Among many things, this library makes animating the addition and removal of list elements simple.
To use this library, we need to first add it to our project. From the command line, make sure you are still in the same location as our todolist project and run the following command:
Click Enter/Return to copy all the necessary things locally into our project’s
node_modules folder. That’s all the setup required. After you’ve done this, in
TodoItems.js, add the following
import statement at the top:
Now all that’s left is to tell our
FlipMove component to animate our list of items. In our
render function, make the following highlighted change:
We’re simply wrapping our
listItems (just before getting them printed) inside a
FlipMove component and specifying the animation duration and the type of easing function to use. That’s it. If you preview your app now, you’ll see that adding and removing items doesn’t just suddenly happen; these items are smoothly animated instead.
Uncontrolled Components vs. Controlled Components
Form elements are interesting. These are elements that contain some state on their own. For example, your text element might have some content in it, or you might have some items already selected in a drop-down list. React is all about centralizing state into its own little world, so it doesn’t like that form elements have their own internal mechanism for storing state. The guidance is to synchronize all the form data inside a React component by using events such as onChange. These components that let React deal with form elements are known as controlled components.
Still, it’s a hassle to have every form element deal with keeping state in sync. The React developers get that as well. The workaround is to do nothing. We simply let form elements deal with their own state and use refs to access the values when needed. That’s what we did in this example. When we have components that defer all state management to the form DOM element, these components are known as uncontrolled components.
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 important, we created an example that shows how the various concepts we learned individually play together. That’s truly actually the important detail.
Now here’s a quick question for you: Does everything we’ve done in this chapter make sense? If so, you’re in good shape to tell your friends and family that you’re close to mastering React. If you still find some areas confusing, I recommend that you go back and reread the chapters that address your shortcomings.
Note: If you run into any issues, ask!
If you have any questions or your code isn’t running like you expect, don’t hesitate to ask! Post on the forums at https://forum.kirupa.com and get help from some of the friendliest and most knowledgeable people the Internet has ever brought together!