Tutorial

Build a React To-Do App with React Hooks (No Class Components)

Draft updated on Invalid Date
author

Kapehe

Build a React To-Do App with React Hooks (No Class Components)

This tutorial is out of date and no longer maintained.

Introduction

Yes, a to-do app, seems like something we’ve all done in the past. The great thing about a to-do app is that it covers all 4 aspects of CRUD; Create, Read, Update, Delete. As a developer, we need to know CRUD and a to-do app is the best way to start that learning or refresh some of the things we may be a little rusty on.

React is the frontend JavaScript library we are going to be using today. Whether you are new to React or are an avid React user, we can all agree on one thing: React is pretty great.

We’ve already done a React To-Do App here at Scotch, but that was with Class components. Today we will have no Class components by integrating React Hooks, React’s newest upgrade feature. Let’s get started.

https://codesandbox.io/s/oj3qm2zq06

React Hooks

React is always improving and growing. The latest update is React 16.7, so new it’s in alpha stages. In this upgrade, we are given the power of React Hooks.

React Hooks allow for functional components to have a state and utilize lifecycle methods.

No longer do we need to rely on just class components for that functionality.

You can learn all about React Hooks here or visit React’s docs on Hooks here.

Starting a React App

Navigate to the place you would like your new application to be located and type:

  1. npx create-react-app react-to-do

Note: Running npx before the command allows for it to be installed if it is not already installed globally on your machine.

Sit back and relax, React is taking care of the initial build for your new application.

Once it is done, you will need to navigate into the project:

  1. cd react-to-do

and then to run the project:

  1. npm run start

and navigate to http://localhost:3000/ to see the spinning React logo in your browser.

React 16.7 currently in alpha

At the time of this writing, React Hooks are in React 16.7 but React 16.7 is in alpha currently. You’ll need to install that specifically until it comes out of alpha.

  1. npm install -S react@16.7.0-alpha.2 react-dom@16.7.0-alpha.2

Using CodeSandbox

CodeSandbox is a great tool to build applications in your browser without having to open up your CLI. Try building this online. Also, be sure to remove the React 16.6 dependencies and add React 16.7.

Styling Your Application

Jump into your src/App.css file and add in the three classes we will be using throughout our app. Styling won’t be the focus of this app, so we’ll keep this short and sweet.

src/App.css

.app {
  background: #209cee;
  padding: 30px;
  height: 100vh;
}

.todo-list {
  background: #e8e8e8;
  border-radius: 4px;
  padding: 5px;
  max-width: 400px;
}

.todo {
  background: #fff;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.15);
  padding: 3px 10px;
  font-size: 12px;
  margin-bottom: 6px;
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

Reading a List of Items. cRud.

With your application running and the styling ready to be used, let’s start on the Read part of CRUD. We’ll want to make a list of things, just so we can Read/view the list.

Adding in State

Go into your src/App.js file and let’s add a state to our component. We are going to be using React Hooks so state will look a little different than what you’re used to in classes.

src/App.js

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn about React" },
    { text: "Meet friend for lunch" },
    { text: "Build really cool todo app" }
  ]);

  // we'll render our todos here ...
  // return <div></div>
}

The component, as we can see, is a functional component. In past versions of React, function components were unable to handle state, but now, by using Hooks, they can.

  • The first parameter, todos is what we are going to name our state.
  • The second parameter, setTodos is what we are going to use to set the state.

We’ve got a writeup on array destructuring if you want to know more info about that [todos, setTodos] syntax here.

The hook of useState is what React uses to “hook” into the state or lifecycle of the component. We then create an array of objects and we have the beginnings of our state.

Comparing to a Class Component

Let’s take a quick detour and see how this would’ve done with classes:

class App extends Component {
  state = {
    todos: [
      { text: "Learn about React" },
      { text: "Meet friend for lunch" },
      { text: "Build really cool todo app" }
    ]
  }

  setTodos = todos => this.setState({ todos });

  render() {
    return <div></div>
  }

}

A lot more typing. React Hooks lets us make that really clean! We’ll continue with our functional component version from above.

Our “Todo” Component

We will want to create a component that we can use later on in the return of the main App component. We will call that Todo and it will pass in the (todo) and show the “text” part of the todo (todo.text), like so:

const Todo = ({ todo }) => <div className="todo">{todo.text}</div>;

Let’s see how we will use that in our App component.

Using Our Todo Variable to Get a List Returned

Go down to the return part of the App component and remove almost everything. We want to empty out that return part especially so that the spinning logo we saw earlier when we navigated to http://localhost:3000, goes away and we have our list being displayed on the page.

By using the JavaScript method, map(), we are able to create a new array of items by mapping over the todo items from state and displaying them by index.

Let’s create a pretty list of items:

src/App.js

  return (
    <div className="app">
      <div className="todo-list">
        {todos.map((todo, index) => (
          <Todo
            key={index}
            index={index}
            todo={todo}
          />
        ))}
      </div>
    </div>
  );

Navigate to your browser and you should see something like this:

Creating New Items to the To-Do List - Crud

Want to create a new item to the list? What if we forgot to add something to our to-do list and don’t want to forget that thing? Let’s give our application the power to Create a new item for our to-do app.

While in the src/App.js file, we are going to want to add a couple of things. At the top we are going to add another component, we’ll call it TodoForm. In this component, we want it to:

  • Start with an empty state for an input field.
  • Be able to update the form by setting the state.
  • Handle the submit.

Setting our Empty State for the Form Input

Remember, we are using React Hooks so state is going to be a little different. To set our state we are going to want to write it like so:

const [value, setValue] = useState("");

The first is the “value” and the second is how we are going to be setting the state. The state starts off empty and as we add things to our state, it will add it to our list of to-do items.

We will want to add in a handleSubmit variable that can handle our addTodo function (we will make that function soon) and add the item to the list. If nothing is in the input box and the user presses “enter”, we want it to not do anything (i.e., not add in an empty tile to the list).

Adding that functionality into a form that has an input box, we should have our code look like this:

src/App.js

function TodoForm({ addTodo }) {
  const [value, setValue] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        className="input"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
    </form>
  );
}

The addTodo function I told you about earlier? Let’s go ahead and build that now. Staying within App.js, under the state of the App component, the function should be able to grab the existing list of items, add on the new item, and display that new list.

src/App.js

const addTodo = text => {
    const newTodos = [...todos, { text }];
    setTodos(newTodos);
  };

Notice the lack of this.state.? With the new React Hooks, we have no more need to use that. Can you use it? Sure, of course. But the new Hooks allow for less typing, more efficiency, and understand that this.state. is going to be implied in certain places.

See that spread operator? The three dots before the todos, that is essentially “copying” the list for us so that we are able to add on the new to-do item. Then using our keyword that we set earlier, we will set the state with setTodos.

By using the TodoForm down in the return of the App component, we will see that input box pop up now. The entire src/App.js file should look like this so far:

src/App.js

import React, { useState } from "react";
import "./App.css";

const Todo = ({ todo }) => <div className="todo">{todo.text}</div>;

function TodoForm({ addTodo }) {
  const [value, setValue] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        className="input"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
    </form>
  );
}

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn about React" },
    { text: "Meet friend for lunch" },
    { text: "Build really cool todo app" }
  ]);

  const addTodo = text => {
    const newTodos = [...todos, { text }];
    setTodos(newTodos);
  };

  return (
    <div className="app">
      <div className="todo-list">
        {todos.map((todo, index) => (
          <Todo
            key={index}
            index={index}
            todo={todo}
          />
        ))}
        <TodoForm addTodo={addTodo} />
      </div>
    </div>
  );
}
  export default App;

Go to your browser and play around. You can now add a to-do item to your list!

Updating Items in the To-Do List to be Completed - crUd

How would we want to update our to-do application? Maybe let’s have the functionality of being able to cross off an item. Let’s get started on that code!

Updating our State

Our state in our App component needs a little extra to it for the “Completed” status to be able to change. We will be adding in another key/value pair to our list of objects. By adding in an isCompleted: false value, we set that to false to begin with and will, when prompted, change that to true.

src/App.js

const [todos, setTodos] = useState([
     {
       text: "Learn about React",
       isCompleted: false
     },
     {
       text: "Meet friend for lunch",
       isCompleted: false
     },
     {
       text: "Build really cool todo app",
       isCompleted: false
     }
   ]);

We will need a function like the addTodo function but this one will be able to “complete” an item. We will want to do some similar things that we did in the addTodo like using the spread operator to grab the current list of items. In this function, we will be changing the isCompleted status to true so that it knows it has been completed. It will then update the state and set the state to the newTodos.

src/App.js

const completeTodo = index => {
     const newTodos = [...todos];
     newTodos[index].isCompleted = true;
     setTodos(newTodos);
   };

By using completeTodo in the Todo function, we are going to be able to fire off that functionality. When the “Complete” button is clicked, it will add in the textDecoration styling and cross out the item. We are using a ternary operator, a feature within ES6 JavaScript, which is a simpler way of doing an if/else statement. This is our way of completing an item on the list and “updating” the list. The code should look as follows:

src/App.js

function Todo({ todo, index, completeTodo }) {
  return (
    <div
      className="todo"
      style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
    >
      {todo.text}

      <div>
        <button onClick={() => completeTodo(index)}>Complete</button>
      </div>
    </div>
  );
}

Dive down to the return of the App component and we’ll add in the following line:

completeTodo={completeTodo}

to look like this in the code:

src/App.js

<div className="card-content">
    {todos.map((todo, index) => (
        <Todo
            key={index}
            index={index}
            todo={todo}
            completeTodo={completeTodo}
        />
    ))}
</div>

Returning to the browser, your to-do app should look something like this when a “Complete” button is clicked.

Now we can read our list, add to our list, and update the completed status of each item. What’s left? Delete an item.

Deleting a To-Do Item - cruD

So you’ve completed an item on your to-do list, the day is over and you want to delete it from your list to be able to start over tomorrow. We are going to want to delete that item completely. Let’s see how we can get that going.

By adding just a couple of lines, we will be able to add in the functionality of deleting an item.

We will go ahead and build the removeTodo function so that when we click on an “X” to delete an item, that will be fired off. That function will be located by the others underneath the state of the App component.

In this removeTodo function, we will again be using the spread operator but once we grab that current list, we will be “splicing” the chosen index off of the array of items. Once that is removed, we will return the new state by setting it with setTodos to be newTodos.

src/App.js

const removeTodo = index => {
     const newTodos = [...todos];
     newTodos.splice(index, 1);
     setTodos(newTodos);
   };

In your Todo function, you will want to add in this line:

<button onClick={() => removeTodo(index)}>x</button>

like this:

src/App.js

function Todo({ todo, index, completeTodo, removeTodo }) {
  return (
    <div
      className="todo"
      style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
    >
      {todo.text}

      <div>
        <button onClick={() => completeTodo(index)}>Complete</button>
        <button onClick={() => removeTodo(index)}>x</button>
      </div>
    </div>
  );
}

You’ll see as well that we are bringing in removeTodo at the top and then using it in the onClick of the “X”.

Adding in the removeTodo in the Todo part of the returning the App component, our “delete” will be fully functional. Add it in here:

src/App.js

<Todo
    key={index}
    index={index}
    todo={todo}
    completeTodo={completeTodo}
    removeTodo={removeTodo}
/>

With that added in, go to your browser and you’ll see a button with an “X” that when clicked, deletes the item completely.

The Final Product

The entire src/App.js file should look like this in the end:

src/App.js

import React, { useState } from "react";
import "./App.css";

function Todo({ todo, index, completeTodo, removeTodo }) {
  return (
    <div
      className="todo"
      style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
    >
      {todo.text}

      <div>
        <button onClick={() => completeTodo(index)}>Complete</button>
        <button onClick={() => removeTodo(index)}>x</button>
      </div>
    </div>
  );
}

function TodoForm({ addTodo }) {
  const [value, setValue] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        className="input"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
    </form>
  );
}

function App() {
  const [todos, setTodos] = useState([
    {
      text: "Learn about React",
      isCompleted: false
    },
    {
      text: "Meet friend for lunch",
      isCompleted: false
    },
    {
      text: "Build really cool todo app",
      isCompleted: false
    }
  ]);

  const addTodo = text => {
    const newTodos = [...todos, { text }];
    setTodos(newTodos);
  };

  const completeTodo = index => {
    const newTodos = [...todos];
    newTodos[index].isCompleted = true;
    setTodos(newTodos);
  };

  const removeTodo = index => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
  };

  return (
    <div className="app">
      <div className="todo-list">
        {todos.map((todo, index) => (
          <Todo
            key={index}
            index={index}
            todo={todo}
            completeTodo={completeTodo}
            removeTodo={removeTodo}
          />
        ))}
        <TodoForm addTodo={addTodo} />
      </div>
    </div>
  );
}

export default App;

What have we learned?

A to-do app can be a great reminder or starting point when it comes to CRUD in web development. Being able to read information, create new information, update existing information, and deleting said information can be powerful in any application.

React Hooks are great. They allow for a more straightforward way of coding and can make your code clear and concise.

Now go have fun adding all your many to-do items to your newly built to-do app. And then have even more fun crossing them off when you finish them!

Happy coding!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Kapehe

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more