DEV Community

Harry Katsaris
Harry Katsaris

Posted on

Creating a Simple Todo App with React🖥️

A todo app is one of the simplest applications you can create, but it can still teach you a lot about React. In this post, we will build a basic todo app with React using functional components, hooks, and state management. The Project consists of 5 sections (6 if you count the file preparation) and in the end of each section I have provided you with a link to a GitHub repo with different branches.Eaxh branch represents every step along the way. The files we'll be working with are located in the src folder.

At any point you can refer to my code on GitHub, or navigate directly to the master branch to check out the entire project 😉

0. React set up and file preparation:

Let's start by creating our app using the cra(create-react-app) tool. In the terminal run npx create-react-app react-todo-list. Then cd react-todo-list and last npm start. More information can be found on react official page

https://beta.reactjs.org/learn/start-a-new-react-project

Continue with cleaning up the App.js and Index.js files. they should look like this:

// ----------- App.js file ---------------------------
import React, { Component } from 'react'
import './App.css';

export default class App extends Component {
  render() {
    return (
      <div>
        <App/>
      </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode
// ------------ index.js file ---------------------------
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

Make a TodoList.js file with a ul element. Add Constructor(props) to manage State in the file. At this point its an empty array:

this.state = { todos: [] } 
Enter fullscreen mode Exit fullscreen mode

Make a todo.js component that returns a div with an <li>{this.props.task}<li> and 2 buttons for “edit” and “delete”. The ‘task’ will be the argument that we are going to work with inside every Todo component we are rendering. It can be named anything e.g. YellowNutCracker, but its a good practise to name our variables and files according to their role/function.
Import the Todo.js file into TodoList.js and add sample data in the state, passing data as JSX:

`this.state = {todos:[task:”Feed the cat”]}`
Enter fullscreen mode Exit fullscreen mode

Now its time to access the data (each task in Todo component) from our state and push it to the array. I like to use the array.map() method for this that creates a new array populated with the results of calling a provided function on every element in the calling array. Then we ‘ll return a Todo React Component with a task property:

const todos = this.state.todos.map(t ⇒ { 
return <Todo task={t.task} />; 
});
Enter fullscreen mode Exit fullscreen mode

Shortly after, render the {todos} array inside our <ul> element. Use JSX syntax (duh!)

<ul> {todos} </ul>
Enter fullscreen mode Exit fullscreen mode

At this point your code should look like this:
https://github.com/Harry2gks/react-todo-list-v2/tree/0.-Setup/src

1. Building the Form

We want the user to add, edit and delete his todos from our app. For that we can build a <form> to accept the user inputs. Lets start by creating the Form.js file. Type rcc to generate the basic template if you have the extension enabled in Vs code, otherwise I reccoment to enable the extension to make your life easier. Create a Constructor(props) function to control the state in this Component and set task as an empty string, since it will accept the user input as a string

this.state = {task: “”}`
`<form>
<label htmlFor=’task’> </label>
<input 
type=”text” 
placeholder=”New Todo” 
id=”task” 
name="task"
value={this.state.task} 
onChange = {this.handleChange} // controls what the user can change
/>`
`<button>Add Todo</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Now it is time for our best friend handleChange() method. Note that this is a custom method, not a standart JS method, like array.map()

`handleChange(evt) {
  this.setState({
    [evt.target.name] : evt.target.value // from the “name” : change the “value”
});`
Enter fullscreen mode Exit fullscreen mode

and don’t forget to bind our method to the component in the Constructor:

this.handleChange = this.handleChange.bind(this);
Enter fullscreen mode Exit fullscreen mode

2. Adding new Todo / Create functionality

Let’s add the Create functionality from C.R.U.D. .For that we are going to make a new method create() that is going to accept an object as a parameter and push it in [todos] array inside our state. Thats why we are going to build it inside our TodoList.js file:

create(newTodo) {
  this.setState({
    todos: […this.state.todos, newTodo] // Take the array from state and add   newTodo object
  });
}
Enter fullscreen mode Exit fullscreen mode

and bind it:

this.create = this.create.bind(this);
Enter fullscreen mode Exit fullscreen mode

Then we need to pass our method as a property inside the <Form> we are rendering here, in our TodoList.js file

<Form createTodo={this.create} />
Enter fullscreen mode Exit fullscreen mode

Now we have the method create() inside our Form.js file as prop called createTodo, and will call it inside the handleSubmit(), passing the state as parameter. The handleSubmit() will be called when the user submits the form, thus creating a new <Todo> and will push the user input as a task to the state. Then we’ll clear the form:

`handleSubmit(evt) {
  evt.preventDefault();
  this.props.createTodo(this.state);
  this.setState({ task:”” })
}`
Enter fullscreen mode Exit fullscreen mode

and bind it:

this.handleSubmit = this.handleSubmit.bind()this;
Enter fullscreen mode Exit fullscreen mode

At this point the code should look like this:
https://github.com/Harry2gks/react-todo-list-v2/tree/C-from-C.R.U.D

3. Adding the Delete functionallity

Deleting is similar to creating, we’will make a delete() function inside our TodoList.js file using the array.filter() method. We ‘ll also pass it as a prop inside our render(), so that we’ll be able to call it inside the handleDelete() function in our Todo.js file. At this point I would like to point out that every <Todo> we’ re rendering requires a react {key} and also an {id} since we'll be deleting using the id. I like to use a library that helps me create different ids for every item called uuid : https://www.npmjs.com/package/uuid

`delete(id) {
    this.setState({
      todos: this.state.todos.filter((t) => t.id !==id),
    });
  }`

`<Todo 
      key={t.id} 
      id={t.id} 
      task={t.task} 
      deleteTodo={this.delete} // delete() passed as a prop inside render()
      />;`

`handleDelete() {
      this.props.deleteTodo(this.props.id)
    }`
Enter fullscreen mode Exit fullscreen mode

and bind it.

Now let’s add the handleDelete() to our button:

<button onClick={this.handleDelete}>Delete</button>
Enter fullscreen mode Exit fullscreen mode

You code should look like this now:
https://github.com/Harry2gks/react-todo-list-v2/tree/D-from-C.R.U.D-/src

4. Editing / Updating Todo

This part is a bit tricky. In order to edit the we’ll need to add some conditinal logic in our Todo.js file to display the editing form when the user is editing or the list of todos when the user is not.
In our state add the boolean logic isEditing: false and borrow the task from the todo:

this.state = {
  isEditing: false,
  task: this.props.task
}
Enter fullscreen mode Exit fullscreen mode

We’ll also make a custom method toggleForm() that is going to display or hide the form as mentioned above. And of course we ‘ll need to create a form:

render() {
  let edit;
  if (this.state.isEditing) {
    edit = (
      <form onSubmit={this.handleSubmit}>
        <input 
        type="text"
        name="task"
        value={this.state.task}
        onChange={this.handleChange}/>
        <button>Update</button>
      </form>
    );
  } else {
    edit = (
    <div>
      <button onClick={this.toggleForm}{>Edit</button>
      <button onClick={this.handleRemove}>Delete</button>
    </div>
    )
  }
  return edit;
}
Enter fullscreen mode Exit fullscreen mode

As we’ve seen before we need our best friends handleChange() and handleSubmit() just like in the Form.js file and our toggleForm() connected with an onClick event to our button.

handleChange(evt) {
  this.setState({
    [evt.target.name]: evt.target.value
  });
}
handleSubmit(evt) {
  evt.preventDefault();
  // take the new task Data and pass it to the parent (todoList)
  this.props.updateTodo(this.props.id, this.state.task);
  this.setState({ isEditing: false });
}
toggleForm() {
  this.setState({ isEditing: !this.state.isEditing });
}
Enter fullscreen mode Exit fullscreen mode

and bind in Constructor. I know, I Know.

Next we need to take the updated task and pass it up to the parent component TodoList.js. To do that we’ll need to define a method and know 2 things: what are we updating -->id<-- and the new task -->updatedTodo<--:

update(id, updatedTask) {
    const updatedTodos = this.state.todos.map((todo) => {
      if (todo.id === id) {
        return { ...todo, task: updatedTask };
      }
      return todo;
    });
    this.setState({ todos: updatedTodos });
  }
      return todo;
    });
    this.setState({ todos: updatedTodos });
  }
Enter fullscreen mode Exit fullscreen mode

and pass the methods to our render inside the

<Todo 
    key={t.id} 
    id={t.id} 
    task={t.task} 
    removeTodo={this.remove}
    updateTodo={this.update}
    />
Enter fullscreen mode Exit fullscreen mode

at this point your code should look like this:

https://github.com/Harry2gks/react-todo-list-v2/tree/U-from-C.R.U.D-

Section 5 --> Mark Todo as “completed”

Lets make sure our Todos can keep track of when they are completed or not. In our Form.js file, when we make a new todo (createNewTodo) we’ll add completed:false boolean logic to handleSubmit method:

handleSubmit(evt) {
    evt.preventDefault();
    this.props.createNewTodo({ ...this.state, id: uuidv4(), completed: false });
    this.setState({ task: "" });
  }
Enter fullscreen mode Exit fullscreen mode

Then add a conditional className in our <li> that displays the task inside the Todo.js:
<li className={this.props.completed ? "completed" : ""}>

and create a .completed class in our App.css:

.completed { 
    colour: gray, 
    text-decoration: line-through 
}
Enter fullscreen mode Exit fullscreen mode

and pass it down in our TodoList when we render the Todo component

render() {
    const todos = this.state.todos.map((t) => {
      return <Todo 
      key={t.id} 
      id={t.id} 
      task={t.task} 
      completed={t.completed} // <------------ 
      removeTodo={this.remove}
      updateTodo={this.update}
      />;
    });
Enter fullscreen mode Exit fullscreen mode

Following the same pattern with edit, we are going to add a button that toggles the form, calls a method in the parent element and updates the state. In our TodoList.js file:

toggleCompleted(id) {
    const updatedTodos = this.state.todos.map((t) => {
      if (t.id === id) {
        return { ...t, completed: !t.completed };
      }
      return t;
    });
    this.setState({ todos: updatedTodos });

this.toggleCompleted = this.toggleCompleted.bind(this); // up top
Enter fullscreen mode Exit fullscreen mode

and then pass it down as a prop to our :
toggleTodo={this.toggleCompleted}.

Since we’re making the <li> clickable, then we ‘ll require an onClick event and our handleChange() method, with a different name handleToggle(). in Todo.js file:

<li className={this.props.completed ? "completed" : ""}
          onClick={this.handleToggle}>
            {this.props.task}</li>

handleToggle(evt) {
    this.props.toggleTodo(this.props.id);
  }

this.handleToggle = this.handleToggle.bind(this); // up top
Enter fullscreen mode Exit fullscreen mode

Code up to here:
https://github.com/Harry2gks/react-todo-list-v2/tree/Toggle-Completed-Todos/src

This article is starting to get a bit long, so i am gonna wrap it up with some styling.
Give the

element inside our App.js file
className=”container” and add the following styles into App.css, or copy my stylesheet directly from github.
.completed{
    color: gray;
    text-decoration: line-through;
}

.container {
    display: block;
    width: 500px;
    margin: 10px auto 100px;
    background-color: #cc9797;
    padding: 0px 10px 10px 10px;
    border-radius: 10px;
    text-align: center;
    color: #7b0e0e;
    font-size: 18px;
  }

ul {
    list-style: none;
}

li {
    margin: 10px;
    font-size: 22px;
}

li:hover {
    cursor: pointer;
}

ul button {
    padding: 2px 2px 2px 2px;
    margin: 2px 4px 2px 4px;
    font-size: 18px;
}

button {
    background: #7b0e0e;
    background-image: -webkit-linear-gradient(top, #7b0e0e, #7a0e40);
    background-image: -moz-linear-gradient(top, #7b0e0e, #7a0e40);
    background-image: -ms-linear-gradient(top, #7b0e0e, #7a0e40);
    background-image: -o-linear-gradient(top, #7b0e0e, #7a0e40);
    background-image: linear-gradient(to bottom, #7b0e0e, #7a0e40);
    -webkit-border-radius: 8;
    -moz-border-radius: 8;
    border-radius: 8px;
    -webkit-box-shadow: 0px 1px 3px #524d52;
    -moz-box-shadow: 0px 1px 3px #524d52;
    box-shadow: 0px 1px 3px #524d52;
    font-family: Arial;
    color: #ebbcbc;
    font-size: 20px;
    padding: 4px 4px 4px 4px;
    text-decoration: none;
}

button:hover {
    background: #c91818;
    background-image: -webkit-linear-gradient(top, #c91818, #610c34);
    background-image: -moz-linear-gradient(top, #c91818, #610c34);
    background-image: -ms-linear-gradient(top, #c91818, #610c34);
    background-image: -o-linear-gradient(top, #c91818, #610c34);
    background-image: linear-gradient(to bottom, #c91818, #610c34);
    text-decoration: none;
}

input {
    padding: 5px;
    margin: 0px 20px;
    border-radius: 8px;
    background-color: #e7bcbc;
    color: #7b0e0e;
}

Our lovely project is done! (finally)

Critisism accepted and appreciated :)

Live Project: https://react-todo-list-v2-lemon.vercel.app/
GitHub https://github.com/Harry2gks

Top comments (0)