Building a To-Do List App Using React - Hooks and Styled Component

Building a To-Do List App Using React - Hooks and Styled Component

Introduction

Hooks were introduced in React 16.8. They allow use of state and other React features by using functional components. There are various types of hooks available in React for example useState, useEffect, useContext among others. For To-do List project we will only be using useState hook. useState - allows adding of state to functional component. Styled-component on the other hand is a popular library that is used to style react applications. It allows writing actual CSS in your JavaScript. You can find out more on All you need to know about styled-components with React.

If you would prefer to follow this tutorial on YouTube its available at

A complete working solution is available on Github

To Do App

The To Do App that we are going to build will allow a user to add a task to a list of to-do items. Once the task is added, the user will be able to mark it as completed once its done. When you click on a task, if it was pending it will be marked as complete by crossing the task with a line. There will be a count that will be displaying both the pending and completed tasks.

Step by Step Guide

1. Create a React App

  • yarn:
    yarn create react-app todo-list
    
  • npm:
    npx create-react-app todo-list
    

cd into todo-list and yarn start OR npm start if using npm.

2. Design the UI

In the src folder, open App.js and get rid of everything between

tags. The App.js file should look as below:

import React from 'react';
import './App.css';

function App() {
 return (
   <div className="App">
        Hello World! Starting a to-do List
   </div>
 );
}

export default App;

We will use styled-components for styling our App. Using the following command install the styled-component package.

npm install styled-components

import the styled-components in our App.js by adding the following in import section.

import styled from "styled-components";

Our first component will be Container div with styling done using styled component. Our App will now look as follows.

import React from 'react';
import './App.css';
import styled from "styled-components";

const Container = styled.div`
  display: flex;
  align-items: center;
  flex-direction: column;
`;

function App() {
 return (
   <Container>
   </Container>
 );
}

export default App;

3. Complete the UI

Other components which includes input, button, span, div will be styled using styled-components and we will end up with the following for our UI definition. Our UI definition will be completed at this point.

import React from 'react';
import './App.css';
import styled from "styled-components";

const Container = styled.div`
  display: flex;
  align-items: center;
  flex-direction: column;
`;

const Button = styled.button`
  display:inline-block;
  flex: 1;
  border: none;
  background-color: teal;
  color: white;
  height: 30px;
  width: 50px;
  border-radius: 2px;
  cursor: pointer;
`;

const Text = styled.input`
  border: 2px solid #000;
  width: 200px;
  padding: 5px;
  border-radius: 2px;
  margin: 5px;
`;

const TaskCount = styled.span`
  margin: 10px;
`;

const Tasks = styled.div`
`;

const TaskCount = styled.span`
  margin: 10px;
`;

const Tasks = styled.div`
`;

function App() {
 return (
  <Container>
      <div>
          <h2>Todo List</h2>
          <Text value={input} />
          <Button>Add</Button>
        <Tasks>
          <TaskCount>
            <b>Pending Tasks</b> 
          </TaskCount>
          <TaskCount>
            <b>Completed Tasks</b>
          </TaskCount>
        </Tasks>
        <div>
          <ul>
              /* List items consisting of tasks will be listed here */
          </ul>
        </div>
        <Button>Clear</Button>
      </div>
    </Container>
 );
}

export default App;

4. Add Tasks and Mark Completed tasks

At this point the UI is completed. We need to add functionality which will enable a user to add new tasks and mark completed tasks.

We will use useState() hook that will store the state. In our imports we add the following:

import React, { useState } from "react";

Using useState() hook, we will initialize the following states which will store state and manage changes to the variables. input - variable to keep track of the task user inputs. todoList - will consist of an array that will have a list of all tasks. Each task is defined as an object which looks as follows:

{
     id: id, //unique id
     task: Task, // a task to be added to the todo list
     complete: false, //false means task is incomplete, true means task is completed
 }

completedTaskCount - will keep track of count of completed tasks.

inside the App function in App.js, we will add the following:

const [input, setInput] = useState("");
const [todoList, setTodoList] = useState([]);
const [completedTaskCount, setCompletedTaskCount] = useState(0);

In the Text and Button components, we will add event handler functions setInput() and handleClick(). The setInput() will set value of user task input while handleClick() will add the task to todoList.

<Text value={input} onInput={(e) =>setInput(e.target.value)} />
<Button onClick={() => handleClick()}>Add</Button>

handleClick() function:

const handleClick = () => {
  const id = todoList.length + 1;
  setTodoList((prev) => [
    ...prev,
    {
      id: id,
      task: input,
      complete: false,
    },
  ]);
  setInput("");
};

5. Display List of tasks

Inside the ul tag we will define a map function that will iterate through the todoList [] array and create li list items for display. Each list item(task) will contain properties which includes: id, complete, onClick() event handler and task. It will look as follows:

<ul>
  {todoList.map((todo) => {
    return (
      <LIST
        complete={todo.complete}
        id={todo.id}
        onClick={() => handleComplete(todo.id)}
        style={{
          listStyle: "none",
          textDecoration: todo.complete && "line-through",
        }}
      >
        {todo.task}
      </LIST>
    );
  })}
</ul>;

The textDecorationstyle will conditionally apply a line-through for completed tasks and pending tasks will not have a line-through. On clicking the Add button, handleComplete(id) function will be called and it will do the following.

  • Modify the complete property of a task object to true or false depending on the previous state.
  • Increment/decrement the completedTaskCount variable depending if the complete property changed to true or false.

The function definition will be as follows:

const handleComplete = (id) => {
    let list = todoList.map((task) => {
      let item = {};
      if (task.id == id) {
        if (!task.complete){
            //Task is pending, modifying it to complete and increment the count
            setCompletedTaskCount(completedTaskCount + 1);
        } 
        else {
            //Task is complete, modifying it back to pending, decrement Complete count
            setCompletedTaskCount(completedTaskCount - 1);
        }

        item = { ...task, complete: !task.complete };
      } else item = { ...task };

      return item;
    });
    setTodoList(list);
  };

6. Final ToDo List The final ToDo List code will look as below. Note that all along we have been modifying the App.js file only.

import { useState } from "react";
import styled from "styled-components";
import "./index.css";

const Container = styled.div`
  display: flex;
  align-items: center;
  flex-direction: column;
`;

const Button = styled.button`
  display:inline-block;
  flex: 1;
  border: none;
  background-color: teal;
  color: white;
  height: 30px;
  width: 50px;
  border-radius: 2px;
  cursor: pointer;
`;

const Text = styled.input`
  border: 2px solid #000;
  width: 200px;
  padding: 5px;
  border-radius: 2px;
  margin: 5px;
`;

const TaskCount = styled.span`
  margin: 10px;
`;

const Tasks = styled.div`
`;

const LIST = styled.li`
    listStyle:"none";
    text-decoration: "line-through";
`;

const App = () => {
  const [input, setInput] = useState("");
  const [completedTaskCount, setCompletedTaskCount] = useState(0);
  const [todoList, setTodoList] = useState([]);

  const handleClick = () => {
    const id = todoList.length + 1;
    setTodoList((prev) => [
      ...prev,
      {
        id: id,
        task: input,
        complete: false,
      }
    ]);
    setInput("");
  };
  const handleComplete = (id) => {
    let list = todoList.map((task) => {
      let item = {};
      if (task.id == id) {
        if (!task.complete){
            //Task is pending, modifying it to complete and increment the count
            setCompletedTaskCount(completedTaskCount + 1);
        } 
        else {
            //Task is complete, modifying it back to pending, decrement Complete count
            setCompletedTaskCount(completedTaskCount - 1);
        }

        item = { ...task, complete: !task.complete };
      } else item = { ...task };

      return item;
    });
    setTodoList(list);
  };

  return (
    <Container>
      <div>
          <h2>Todo List</h2>
          <Text value={input} onInput={(e) =>setInput(e.target.value)} />
          <Button onClick={() => handleClick()}>Add</Button>
        <Tasks>
          <TaskCount>
            <b>Pending Tasks</b> {todoList.length - completedTaskCount}
          </TaskCount>
          <TaskCount>
            <b>Completed Tasks</b> {completedTaskCount}
          </TaskCount>
        </Tasks>
        <div>
          <ul>
            {todoList.map((todo) => {
              return (
                <LIST
                  complete = {todo.complete}
                  id={todo.id}
                  onClick={() => handleComplete(todo.id)}
                  style={{
                    listStyle: "none",
                    textDecoration: todo.complete && "line-through",
                  }}
                >
                  {todo.task}
                </LIST>
              );
            })}
          </ul>
        </div>
      </div>
    </Container>
  );
};

export default App;

Conclusion

Congratulations! You have now built a To do list App using React hooks and styled-components. You have also gone through other aspects of React and ES6 JavaScript. Its quite straight forward to develop. The above boilerplate can be used to extend the App to include more functionalities around the App. You can do the following if you would like to learn more.

  • Once task is completed, instead of strike-through you can make it to disappear.
  • Add a button below the list of tasks called "Clear" that when clicked it clears all the tasks.

Feel free to comment below incase you need further assistance.