Creating a Todo List App with React (using Hooks and Contexts)
Tags: javascript, beginners, react, tutorial
Published: 2020-07-04 16:30:24

---
title: Creating a Todo List App with React (using Hooks and Contexts)
published: true
description: Learn how to create your own todo list application with React using React Hooks and Contexts.
tags: javascript, beginners, react, tutorial
cover_image: https://i.imgur.com/6K2yC4R.png
---

One of the things that has worked out for me while learning a new language or framework is to create a small app as soon as I have learned a few crucial concepts. What better way to learn than to apply concepts directly to a practical project? In my case, I learned about React Hooks and Contexts a few days ago. It took me a couple of days to get a grasp of the idea, but I managed to create my first React app on day 5 of learning React. In this blog post I will go into the details of the app and provide my favorite resources when it comes to learning React.

The app is a simple to-do list app, but I tried my best to expose myself to concepts that seem daunting to some (including myself) in the beginning. These were primarily Hooks and Contexts. By reading this blog post, I hope that you'll gain a deeper understanding of Hooks and Contexts and you'll have made your own custom hook (yay!). Check it out:

![Demo of to-do list app showcasing its functionalities.](https://dev-to-uploads.s3.amazonaws.com/i/sbn7hzep0ifothewhi8y.gif)

Alright! Let's get started with React Hooks.

## Let's talk about React Hooks

You've probably heard this term many times while learning React, and there's a reason for that. React hooks play a crucial part in almost every React app you'll create. That's why it is important to understand what they are. Let's take a look at the official React docs and see what React hooks are ([link](https://reactjs.org/docs/hooks-overview.html)):

> Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes.

Let's make sense of that. Before hooks, if you ever wanted to use states in your components, you needed to use classes. States are objects that hold information, when states are updated it forces the component to re-render. With hooks, you can now incorporate states within your functional components using `useState()`.

So... what states will we need in the case of our to-do list app? The first question to think about is what are some things that you are storing/holding? What are the things, that when updated, you'd like the component to re-render? What first comes to mind is an array of tasks. A user should be able to write a task, click the add button and have the task appear in the table below. So, we'll need to use states to keep track of these tasks. A task object will have the following structure:

```javascript
{
  id: v4(),
  task,
  complete: false
}
```

Where `id` is a unique identifier using `v4()` from the `uuid` library. We'll use this identifier when we want to refer to a specific task. For example, when a user wants to complete a task, it would be easier if we had a unique identifier for us to know which task the user is referring to.

## A Primer on React Contexts

Before we start talking about React Contexts, it's important to understand the different approaches you can take when it comes to state management for this app.

One approach is to have the necessary states being handled by the component itself, for example, managing the state of tasks in the TaskList component. However, over time, this approach will result in states being all over the place, and in larger applications it will be more difficult to debug.

Another approach is to handle all states at the root of your app. That is, storing all possible states that your entire app needs to manage in the App component. You might think that this would solve the issue, however, let's say you have a Task component that takes care of displaying a single task. You also have a TaskList component that contains a bunch of Task components. When you complete a task, you must pass the new state backwards all the way to the root. That would mean that the state must be passed from Task to the TaskList to the App. Now think about what would happen if instead of a single stop in between, you had 5+ components. This nesting will result in a very inefficient way of passing state changes between components that don't even care about the change of the state. That means, when you complete a task, the TaskList doesn't care and doesn't need that information, the App needs it. There are probably other ways to manage states, but if you're still confused, this might clear it up:

![Storing states in the App component and having to pass through all intermediary states](https://i.imgur.com/PdtCGbY.png)

This is where contexts come in. You can setup a context provider and wrap it around your app or the part of the app that uses the to-do list. This context provider will let us have the best of both worlds. We will still store our states in a single location, but when passing data around, it doesn't need to pass through components that don't need this data. In this case, the Task component will be the context consumer as it will make use of this context provider to pass data directly to the root of the app. Visually, this is what's happening:

![Storing states in the root of the app and passing data directly to the state holder](https://i.imgur.com/EDwLUmr.png)

Now that you see the advantage of using React contexts, let's create our own context provider and a custom hook that allows us to communicate with that context. That way, the other components can pass data directly to the context without dealing with any intermediary components.

## Writing our own Context Provider

I will first show you the code of our context provider (stored in `TaskProvider.js`), and then we can walk through what the code actually does:

```javascript
// 1
import React, {createContext, useState, useContext} from 'react'
import { v4 } from "uuid"

// 2
const TaskContext = createContext()
export const useTasks = () => useContext(TaskContext)

export default function TaskProvider({ children }) {
  // 3
  const [tasks, setTasks] = useState([])

  // 4
  const addTask = task =>
      setTasks([
          ...tasks,
          {
              id: v4(),
              task,
              complete: false
          }
      ])

  // 5
  const setStatusTask = (id, status) => {
      setTasks(tasks.map(t => t.id === id ? {...t, complete: status} : t))
  }

  // 6
  return (
      <TaskContext.Provider value={{ tasks, addTask, setStatusTask }}>
          { children }
      </TaskContext.Provider>
  )
}
```

1. In the first line we import React and two hooks (`useState` and `useContext`) that we'll be using, as well as `createContext`.
2. Here we use createContext to create, well... a context. Context consumers can access the states. We also create our own hook—`useTasks` that calls `useContext` on the context we just created. This is just to make it easier for other components to get access to the tasks array and other necessary helper functions.
3. We use the `useState` hook and pass it the default value, a list of tasks which will be an empty array in our case. `useState` returns an array of two objects, the value of the state (i.e. the array of tasks) and a function that can be used to change that value.
4. Here we create an `addTask` function that other components will be able to use in order to add a task. We use `setTasks` to change `tasks` to what it originally was plus the new task with a unique `id` using `v4()`. Note that we could have skipped this and just gave components access to `setTasks`. The problem with that is it gives other components access to functionality you might not want to be possible, like deleting a task.
5. We create a `setStatusTask` function that takes in the id of the task and its new status, and maps all tasks to: 1) itself if the `id` doesn't match or 2) to a new task where `complete` is set to `status` if the `id` matches.
6. We wrap the children in our Context Provider so that they have access to it and then we return.

Now that we have our Context Provider, the rest should be easy. We need to create a couple of components. Namely, we need to create a `Task` component that represents a single row in the table of tasks. We need a `TaskList` component which represents the table holding the list of tasks. Finally, we need a `NewTaskForm` component that takes care of providing an input for users to add a new task.

## Coding up our React Components

Let's start with the `Task` component (in the `Task.js` file):

```javascript
// 1
import React from 'react'
import { useTasks } from './TaskProvider'

export default function Task({ id, task, complete }) {
  // 2
  const { setStatusTask } = useTasks()

  // 3
  const checkTask = e => setStatusTask(id, e.target.checked)

  return (
    <tr>
      <td>
        // 4
        <input type="checkbox"  onChange={checkTask} />
      </td>
      <td>
        // 5
        <span className={ complete ? 'task-done' : '' }>{ task }</span>
      </td>
    </tr>
  )
}
```

1. As before, we're just importing React and our custom hook, `useTasks` from the `TaskProvider` in order to get access to the `setStatusTask` function when a user completes a task.
2. Here we use object destructuring to extract `setStatusTask` from `useTasks()`.
3. We create a function `checkTask` that will be called every time a user clicks on the checkbox of a task.
4. Here we create a checkbox with `onChange` set to `checkTask` in order to achieve the functionality in the previous point.
5. Here we display the task. The task can have the `task-done` class in which we have the corresponding CSS to add a strike-through effect indicating that the task is done. We determine whether a task should have this class or not depending on the `complete` variable of the task object.

Now let's create our `TaskList` component (in the `TaskList.js` file):

```javascript
// 1
import React from 'react'
import Task from './Task'
import { useTasks } from './TaskProvider'

export default function TaskList() {
  // 2
  const { tasks } = useTasks()
  return (
    <table>
      <tbody>
        {
          // 3
          tasks.map((task, i) => 
            <Task key={i} {...task} />
          )
        }
      </tbody>
    </table>
  )
}
```

1. We're importing React, the `Task` component and our custom hook, `useTasks` in order to get the list of tasks the user has.
2. We use object destructuring to extract `tasks` from `useTasks()`.
3. For each task in the array of tasks, we transform it into a Task component with a unique key (to avoid console errors) and pass in the task object.

And finally, let's create the `NewTaskForm` component (in the `NewTaskForm.js` file):

```javascript
// 1
import React, { useState } from 'react'
import { useTasks } from './TaskProvider'

export default function NewTaskForm() {
  // 2
  const [task, setTask] = useState('')
  const { addTask } = useTasks()
  
  // 3
  const submit = e => {
    e.preventDefault()
    addTask(task)
    setTask('')
  }

  return (
    // 4
    <form onSubmit={submit}>
      // 5
      <input type="text" value={task} placeholder="Get some eggs..." onChange={e => setTask(e.target.value)} required />
      <button>Add</button>
    </form>
  )
}
```

1. We import React, the `useState` hook to keep track of what the user is writing in the text field and our custom hook, `useTasks` to get access to `addTask`.
2. We use the `useState` hook with a default value of `""` to monitor the current task the user is inputting. We use array destructing to get `task` (the current string the user is writing) and `setTask`, the function we'll use when we want to modify `task`.
3. Here we create a `submit` function that will be called when the user clicks the "Add" button. It will use `addTask` to add the task to our context. It will also reset `task` which as a result clears the input.
4. We create a form with `onSubmit` set to `submit` so that the function gets called when the user submits the form.
5. Finally we create an input with a value of `task` and `onChange` set to an arrow function that uses `setTask` to change the value of `task` to the current value of the text field.

That is all for the components we need. Now we have to connect everything together to get a functional app.

## Connecting the dots

Let's now use all of our components to connect everything together. In the `App.js` file we have the following:

```javascript
// 1
import React from 'react'
import './App.css'
import './Todo.scss'
import NewTaskForm from './NewTaskForm'
import TaskList from './TaskList'

function App() {
  // 2
  return (
    <div className="container">
      <h1 className="title">Todo List</h1>
      <NewTaskForm />
      <TaskList />
    </div>
  )
}

export default App
```

1. We import React, the CSS files and the two components. Namely, `NewTaskForm` and `TaskList`.
2. Here we render a div containing the title, the form and the task list.

The only other change we need to make is in the `index.js` file where we use the `TaskProvider` component we created to wrap the entire app in the context:

```javascript
// 1
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import TaskProvider from './TaskProvider'

// 2
ReactDOM.render(
  <TaskProvider>
    <App />
  </TaskProvider>,
  document.getElementById('root')
)
```

1. We import React, ReactDOM, the CSS, the `App` and `TaskProvider` components.
2. We render the `App` component within the `TaskProvider` to give the entire app access to the context. Note that we could've wrapped a portion of the app instead, but for the sake of simplicity we'll wrap the whole app.

That's it, the app is now ready for testing, but first, let's add some styles!

## Finishing Touches and Styling

Since this blog post focuses on the React aspect of this app, I will not be explaining the scss below, however feel free to reach out to me if something doesn't make sense. The only important thing to keep in mind is the `.task-done` class which sets `text-decoration` to `line-through` to get the strike-through effect. Here is the code for styles (in the `Todo.scss` file)

```scss
@import url('<https://fonts.googleapis.com/css2?family=Yellowtail&display=swap>');

body {
  width: 100%;
  height: 100%;
  background-color: #A6979C;

  
  .container {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 600px;
    max-width: calc(100% - 20px);
    margin: 0 auto;

    .title {
      font-family: 'YellowTail', sans-serif;
      font-size: 32pt;
      color: #42383B;
    }
    
    .task-done {
      text-decoration: line-through;
    }

    form {
      width: 100%;
      display: flex;

      input[type="text"] {
        height: 30px;
        width: 100%;
        border: 1px solid #ccc;
        border-radius: 5px 0 0 5px;
        padding: 5px;
        outline-color: #42383B;
        font-size: 14pt;
      }

      button {
        background-color: #42383B;
        color: #fff;
        border: 0;
        padding: 0 20px;
        border-radius: 0 5px 5px 0;
        font-size: 14pt;
      }
    }

    table {
      width: 100%;
      border-collapse: collapse;

      td:first-child {
        table-layout: fixed;
        width: 20px;
      }

      td:nth-child(2) {
        padding: 10px 20px;
        font-size: 14pt;
      }

      tr:nth-child(even) {
        background-color: #8E7B81;
      }
    }
  }
}
```

## Conclusion

After this long in-depth blog post about how to create a simple to-do list app, I hope you learned a thing or two about React Hooks and Contexts and how you can utilize them within your own applications. If it was hard to understand, don't worry. It took me a couple of days to understand what was going on, but it feels good to be able to put all of these concepts together into a single project.

As I said before, some of the resources I used to learn about these include:

- Learning React By Alex Banks and Eve Porcello.
- The official React documentation. Most importantly in this post:
    - [Hooks at a Glance](https://reactjs.org/docs/hooks-overview.html)
    - [React Contexts](https://reactjs.org/docs/context.html)

If you have any questions, reach out to me, I'll be more than happy to help. Also, make sure you follow me on Twitter [@oohsinan](https://twitter.com/oohsinan) where I post about full-stack development (mostly React).