Build a Todo List App

Welcome to our comprehensive tutorial on building a Todo List App using JavaScript!

This step-by-step guide will walk you through creating a functional and stylish todo list application.

By the end of this tutorial, you’ll have gained valuable insights into JavaScript programming and DOM manipulation, empowering you to create your own dynamic web applications.

Let’s dive in and start building your to-do list from scratch!”

Understanding JavaScript and its role in creating a todo list app

JavaScript is a versatile and powerful programming language that allows developers to create dynamic and interactive web applications.

It is the go-to for front-end development, enabling the creation of engaging user interfaces and enhancing the overall user experience.

In the context of creating a Todo List app, JavaScript plays a crucial role in implementing the functionality of adding, deleting, and manipulating tasks.

With JavaScript, you can easily handle user interactions, update the app’s state in real time, and dynamically apply changes to the user interface.

To start with JavaScript, you need a good understanding of its syntax, data types, and control flow. Additionally, knowledge of HTML and CSS is beneficial as they work together with JavaScript to create a fully functional web application.

Setting up the development environment

In this tutorial, we are going to use VS Code, feel free to use whatever IDE of your choice. We will also be building the todo app using Reactjs. So make sure Nodejs is installed in your system.

VS code is our text editor, Nodejs is the cross-platform JavaScript runtime environment, and Reactjs is a JavaScript library that will make our life much easier when developing the app.

Creating the basic structure and layout of the todo list app

To summarize, we need a text box, a check box, a delete icon, a button to save items, and an area where we would like to display the items on the list.

To use the power of Reactjs, we will split all these into components like the following.

<TodoContainer> // Wrapper (orange part on the image above)
  <List/> // Display items (blue part)
  <TextField/> // Text input field (black part)
  <Button/> // Save button (green part)
</TodoContainer>

Before writing code, let’s create the app using the Vite create command. I will be using NPM; you can also use pnpm or Yarn.

npm create vite@latest

You will be asked to enter your app name. Let’s do with todo-app

Press enter when you are done typing the app name. Then select React as the template.

then select typeScript

And we are ready to start coding.

Navigate inside the folder todo-app, and run the command as instructed.

Once you do that, this should be somewhat the screen you have

Open your browser and type http://localhost:5173

The screen should look like the following

Before we start, I would like to change the port at which the app is served. I like the port 3000. Let’s ask Vite to serve the app on the port 3000 instead of the default port 5173 .

// vite.config.ts

import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: { port: 3000 },
})

Now, we can start creating our components.

Here is what the simple version should look like

// List.tsx

export const List = ({ list }: { list: string[] }) => {
  return list.map((el, index) => <div key={index}>{el}</div>)
}

// TextField.tsx

export const TextField = ({
  state,
  setState,
}: {
  state: string
  setState: (state: string) => void
}) => {
  return <input value={state} onChange={(e) => setState(e.target.value)} />
}

// TodoContainer.tsx

import { ReactNode } from "react"

export const TodoContainer = ({ children }: { children: ReactNode }) => {
  return <div>{children}</div>
}

// App.tsx

import { useState } from "react"

import { TodoContainer } from "./components/TodoContainer"
import { List } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"

function App() {
  const [input, setInput] = useState<string>("")
  const [list, setList] = useState<string[]>([""])

  const handleSave = () => {
    const newList = [...list, input]

    setList(newList)

    setInput("")
  }

  return (
      <div className="card">
        <TodoContainer>
          <List list={list} />
          <TextField state={input} setState={setInput} />

          <button onClick={handleSave}>Save</button>
        </TodoContainer>
      </div>
  )
}

export default App

Now, we can add tasks to the list. That is all about it. But the app looks very bad.

Now, let’s add the functionality to delete an item from the list.

For this, we need to alter the type of list. The List component becomes

// List.tsx

import { Trash } from "./Trash"

export type ListProp = {
  content: string
  id: number
}

export const List = ({
  list,
  setList,
}: {
  list: ListProp[]
  setList: (arg: ListProp[]) => void
}) => {
  return (
    <div>
      {list.map((el) => (
        <div key={el.id}>
          {el.content}
          <Trash setList={setList} list={list} index={el.id} />
        </div>
      ))}
    </div>
  )
}

We added the trash icon, here is its content

// Trash

import { ListProp } from "./List"

export const Trash = ({
  list,
  setList,
  index,
}: {
  list: ListProp[]
  setList: (arg: ListProp[]) => void
  index: number
}) => {
  function handleDelete() {
    const newList = list.filter((element) => element.id !== index)

    setList(newList)
  }

  return (
    <button onClick={handleDelete}>
      <svg aria-hidden="true" fill="none" width={20} height={20} color="red">
        <path
          stroke="currentColor"
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"
        />
      </svg>
    </button>
  )
}

We need to adjust the App.tsx file

// App.tsx

import { useState } from "react"

import { TodoContainer } from "./components/TodoContainer"
import { List, ListProp } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"

function App() {
  const [input, setInput] = useState<string>("")
  const [list, setList] = useState<ListProp[]>([])

  const handleSave = () => {
    const newList = [...list, { content: input, id: Math.random() }]

    setList(newList)

    setInput("")
  }

  return (
    <>
      <div className="card">
        <TodoContainer>
          <List list={list} setList={setList} />
          <TextField state={input} setState={setInput} />

          <button onClick={handleSave}>Save</button>
        </TodoContainer>
      </div>
    </>
  )
}

export default App

We added one more property to the data structure of list. Now, each item on our list has an ID that allows us to target it when we want to remove it from the list.

Now, we are able to add and delete items from the list.

Implementing features: marking tasks as complete

Let’s add the CheckBox component

// CheckBox.tsx

export const CheckBox = ({
  isComplete,
  onChange,
}: {
  isComplete: boolean
  onChange: (arg: boolean) => void
}) => {
  return (
    <input
      type="checkbox"
      checked={isComplete}
      onChange={(e) => onChange(e.target.checked)}
    />
  )
}

The List component becomes

// List.tsx

import { CheckBox } from "./CheckBox"
import { Trash } from "./Trash"

export type ListProp = {
  content: string
  id: number
  isComplete: boolean
}

export const List = ({
  list,
  setList,
}: {
  list: ListProp[]
  setList: (arg: ListProp[]) => void
}) => {
  function handleChange(currentItem: ListProp) {
    const newList = {
      id: currentItem.id,
      content: currentItem.content,
      isComplete: !currentItem.isComplete,
    }

    const alteredList = list.map((el) => {
      if (el.id === currentItem.id) {
        return newList
      }

      return el
    })

    setList(alteredList)
  }

  return (
    <div>
      {list.map((el) => (
        <div key={el.id}>
          {el.content}
          <CheckBox
            isComplete={el.isComplete}
            onChange={() => handleChange(el)}
          />
          <Trash setList={setList} list={list} index={el.id} />
        </div>
      ))}
    </div>
  )
}

And the App.tsx becomes

// App.tsx

import { useState } from "react"

import { TodoContainer } from "./components/TodoContainer"
import { List, ListProp } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"

function App() {
  const [input, setInput] = useState<string>("")
  const [list, setList] = useState<ListProp[]>([])

  const handleSave = () => {
    const newList = [
      ...list,
      { content: input, id: Math.random(), isComplete: false },
    ]

    setList(newList)

    setInput("")
  }

  return (
    <div className="card">
      <TodoContainer>
        <List list={list} setList={setList} />
        <TextField state={input} setState={setInput} />

        <button onClick={handleSave}>Save</button>
      </TodoContainer>
    </div>
  )
}

export default App

Styling the todo list app to enhance user experience

Let’s add a few styles to the app to enhance the user experience

We will adjust the App.css file and add the CSS classes whenever needed. Check this file for the changes.

Conclusion and next steps for further enhancing the todo list app.

We’ve successfully developed a to-do app using ReactJS, but we made some compromises and shortcuts in the process. While we take pride in our achievement, we recognize the importance of addressing areas that could be enhanced or improved.

Currently, we utilize Math.random() to generate IDs when adding new items to the list. While it’s unlikely, there’s a potential risk of having two items with the same ID, introducing a bug into the app. To mitigate this possibility, it would be advisable to replace Math.random() with a more suitable method for generating unique IDs.

Currently, to add an item to the list, we’re required to click the Save button. A notable enhancement would be to enable adding items using the enter key on the keyboard, providing a more streamlined and user-friendly experience.

When you reload the page, the app loses its current state. It would be beneficial to save the to-do list in the local storage, allowing you to refresh the page without losing the previously entered to-dos.

Feel free to check the whole code on GitHub and the live version of the app here.

Feel invited to check how I deployed this React app to Github Pages.

Leave a Comment