DEV Community

Cover image for How to Build a to-do list with typescript & local-storage 😊
Melbite blogging Platform
Melbite blogging Platform

Posted on • Originally published at melbite.com

How to Build a to-do list with typescript & local-storage 😊

Hello my felow software developers, I Love Typescript that's why I created this project for you.

Why I love typescript? Read this article here

Enough talk 😊 , lets do this...

Install typescript if not installed.

sudo npm i -g typescript
Enter fullscreen mode Exit fullscreen mode

In this tutorial we are going to make use of snowpack bundler template to set up our environment. You can make use of webpack or any other bundler.

1. How to set-up/install snowpack

npx create-snowpack-app to-do-app --template @snowpack/app-template-blank-typescript

Enter fullscreen mode Exit fullscreen mode

2. To start the server run

npm start
Enter fullscreen mode Exit fullscreen mode

3. Navigate to public folder as set up your index.html file like this,

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="/dist/index.js" type="module" defer></script>
    <title>ToDo List - Typescript</title>
    <style>
      #list{
        list-style: none;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <li id="list"></li>
    <form id="new-task-form">
      <input type="text" id="new-task-title">
      <button type="submit">Add</button>
    </form>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In your html file Import your script file in your html file like

<script src="/dist/index.js" defer></script>
Enter fullscreen mode Exit fullscreen mode

4. Lets Do the magic with Typescript 😊

Navigate to your index.tsx file in your src folder.

Query the selectors in the index.ts file

const list = document.querySelector("#list")
const form = document.getElementById("new-task-form")
const input = document.querySelector("#new-task-input")
Enter fullscreen mode Exit fullscreen mode

While using typescript you have to specify the type like this

const list = document.querySelector<HTMLUListElement>("#list")
const form = document.getElementById("new-task-form") as HTMLFormElement | null
const input = document.querySelector<HTMLInputElement>("#new-task-input")

Enter fullscreen mode Exit fullscreen mode

Let's query and accept form input and prevent default form behaviors like submission

form?.addEventListener("submit", e => {
  e.preventDefault()

  // validate the input and if empty return null
  if(input?.value == "" || input?.value == null) return
  // create a new task with the following properties
  const newTask: Task ={
    id: uuidV4(),
    title: input.value,
    completed: false,
    createdAt: new Date()
  }
  // call the function that add an item to the list
 //Note: this function is not yet created, we shall create it bellow
  addListItem(newTask)
  // clear input field after submit
  input.value = ""
})
Enter fullscreen mode Exit fullscreen mode

In order to assign every todo task to a unique id as used in the above code, we have to install the uuid package like this

npm install uuid
Enter fullscreen mode Exit fullscreen mode

Note: not every library build has all the packages needed, Whenever we run this app, typescript will throw an error stating

Could not find a declaration file for module 'uuid'. '/home/evans/JAVASCRIPT/ToDo-ts/node_modules/uuid/dist/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/uuid` if it exists or add a new declaration (.d.ts) file containing `declare module 'uuid';`ts(7016)
Enter fullscreen mode Exit fullscreen mode

To fix this problem we have to istall the types package

npm i --save-dev @types/uuid
Enter fullscreen mode Exit fullscreen mode

To use the uuid library import it like this,

import {v4 as uuidV4} from 'uuid'
Enter fullscreen mode Exit fullscreen mode

Now let's create a function that will add a new item to the list.
In order to pass the types and reduce file size for clean code we define types independently and pass them as parameters in a function.
This is how you define the types

type Task = {
  id: string
  title: string,
  completed: boolean,
  createdAt: Date
}
Enter fullscreen mode Exit fullscreen mode

And then pass the type as parameter in the function below

const addListItem = (task: Task) => {
  const item = document.createElement("li")
  const label = document.createElement('label')
  const checkbox = document.createElement('input')
  checkbox.type = "checkbox"
  checkbox.checked = task.completed

  // compine all the field/ elements into one using the array append method
  label.append(checkbox, task.title)
  item.append(label)
  list?.append(item)
}

Enter fullscreen mode Exit fullscreen mode

Storing the to-do's in the browser local storage

For future reference we have to store our list to the local storage. Whenever you refresh your browser the list still exist until you clear the storage.

// create local storage
const tasks: Task[] = loadTasks()
//loop through the tasks in the list and append them in the array
tasks.forEach(addListItem)
Enter fullscreen mode Exit fullscreen mode

Create a function to save the task to local storage.

const saveTasks = () => {
  localStorage.setItem("TASKS", JSON.stringify(tasks))
}
Enter fullscreen mode Exit fullscreen mode

Create a function that retrieves all the data from the local storage and return to the front-end

function loadTasks():Task[] {
  const taskJson = localStorage.getItem('TASKS')
  if(taskJson == null) return []
  return JSON.parse(taskJson)
}
Enter fullscreen mode Exit fullscreen mode

4. Update the local storage

Your final index.ts file should look like this.

import {v4 as uuidV4} from 'uuid'
console.log("Hello world")

type Task = {
  id: string
  title: string,
  completed: boolean,
  createdAt: Date
}
const list = document.querySelector<HTMLUListElement>("#list")
const form = document.getElementById("new-task-form") as HTMLFormElement | null
const input = document.querySelector<HTMLInputElement>("#new-task-title")

// create local storage
const tasks: Task[] = loadTasks()
tasks.forEach(addListItem)

form?.addEventListener("submit", e => {
  e.preventDefault()

  // validate the input and if empty return null
  if(input?.value == "" || input?.value == null) return

  // create a new task with the following properties
  const newTask: Task ={
    id: uuidV4(),
    title: input.value,
    completed: false,
    createdAt: new Date()
  }

  tasks.push(newTask)
  // call the function that add an item to the list
  addListItem(newTask)
  // clear input field after submit
  input.value = ""
})

// in order to pass the types and reduce file size for clean code we define types independendly and pass them as parameters in the function bellow

function addListItem  (task: Task){
  const item = document.createElement("li")
  const label = document.createElement('label')
  const checkbox = document.createElement('input')
  checkbox.addEventListener("change", () => {
    task.completed =checkbox.checked
    saveTasks()
  })
  checkbox.type = "checkbox"
  checkbox.checked = task.completed

  // compine all the field/ elements into one using the array append method
  label.append(checkbox, task.title)
  item.append(label)
  list?.append(item)

}

const saveTasks = () => {
  localStorage.setItem("TASKS", JSON.stringify(tasks))
}

function loadTasks():Task[] {
  const taskJson = localStorage.getItem('TASKS')
  if(taskJson == null) return []
  return JSON.parse(taskJson)
}

Enter fullscreen mode Exit fullscreen mode

Recap

Making use of Typescript enables one to catch bugs in development mode. This prevents shipping bugs to production.

Was this tutorial helpful? Leave a comment below.

Article originally published at melbite.com/to-do-app-with-typescript

Get the source code here

Top comments (0)