In this post, I will talk about how to build a simple todo list app with Reactjs and Typescript. To build a todo list app with Reactjs is quite simple however, to build it with Typescript requires a little extra knowledge. Especially when it comes to defining event handlers. In this post we will build a todo list with reactjs, typescript and yarn.

Todo list with reactjs, typescript

To start this blogpost, I will just start this post by sharing the sample code on how to build it. I will update the post later and describe things there.

Step 0: Yarn create-react-app with Typescript

yarn create-react-app todolistwithyarn --template typescript

Step 1: Define state variables

  const [todo, setTodo] = React.useState("");
  const [todoList, setTodoList] = React.useState<string[]>([]);
  const [editTodoIdx, setEditTodoIdx] = React.useState<number | null>();
  const [doneMap, setDoneMap] = React.useState<typeof CustomMap>({});

The CustomMap data structure would be defined outside the App() definition, and it will look like,

var CustomMap: { [key: string]: any } = {};

Step 2: Update render function in App.tsx

Iin the app.tsx file generated delete all the sample code generated and replace all the code in the return function with the following.

  return (
    <div className="App">
      <header>
        <h2> React todo list with Typescript</h2>
      </header>
      <div>
        <div>
          <div className='TodoArea'>
            <div>
              <input type='text' placeholder='enter todo' onChange={handleChange} value={todo} />
              <button onClick={enterTodo}> Save</button>  
            </div>
            <ul>
              {todoList.map( (t:string, idx: number) => (
                <div className='Todo' key={idx}>
                  {doneMap[idx] ? (
                    <input type='checkbox' checked value={""+idx} onChange={markDoneChange} />
                  ): (
                    <input type='checkbox' value={""+idx} onChange={markDoneChange} />
                  )}
                  <li> {t} </li> 
                  <p style={{marginLeft: 10}}>
                    <button value={""+idx} onClick={editTodo}> Edit</button>
                    <button style={{marginLeft: 10}} value={""+idx} onClick={deleteTodo}> Delete</button>
                    <button style={{marginLeft: 10}} value={""+idx} onClick={markDone}> Done</button>
                  </p>
                </div>
              ))}
            </ul>
          </div>
        </div>
      </div>
    </div>
  );

Replace the css code in your App.css file with the following classes
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}
.TodoArea {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.Todo {
  display: flex;
  flex-direction: row;
  align-items: center;
  list-style: none;
  border-bottom: goldenrod 1px solid;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Step 3: Define a function to save todo

In the context of this post, let us call it enterTodo

  const enterTodo: MouseEventHandler<Element> = () => {
    const existingList = getExistingList();
    console.log(todo);
    if (editTodoIdx != null) {
      existingList[editTodoIdx] = todo;
    } else {
      existingList.push(todo);
    }
    localStorage.setItem("todoList", JSON.stringify(existingList));
    setTodoList(existingList);
    setTodo("");
    setEditTodoIdx(null);
  };

Step 4: Define a function to edit todo

  const editTodo: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    let temp = btn.target as HTMLTextAreaElement;
    const existingList = getExistingList();
    const nBeingEdited = Number(temp.value);
    const todo = existingList[Number(temp.value)];
    setTodo(todo);
    setEditTodoIdx(nBeingEdited);
  }

Step 5: Define a function to delete todo

  const deleteTodo: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    const idxNoStr = (btn.target as HTMLTextAreaElement).value;
    const idx = Number(idxNoStr);
    const existingList = getExistingList();
    existingList.splice(idx, 1);
    saveTodoList(existingList);
  }

Step 6: Mark todo done

  const saveDoneTodo = (idxNoStr: string) => {
    const doneMap: typeof CustomMap = JSON.parse(localStorage.getItem('doneMap') ?? "{}");
    const idx = Number(idxNoStr);
    doneMap[idx] = !doneMap[idx];
    setDoneMap(doneMap);
    localStorage.setItem('doneMap', JSON.stringify(doneMap));

  }
  const markDone: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    const idxNoStr = (btn.target as HTMLTextAreaElement).value;
    saveDoneTodo(idxNoStr);
  }

For the one above, I think it is best we split them into two functions to gain a better understanding.

Todo list with reactjs, typescript: final App.tsx

import React, { ButtonHTMLAttributes, ChangeEvent, ChangeEventHandler, EventHandler, MouseEventHandler, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';

var CustomMap: { [key: string]: any } = {};
function App() {
  const [todo, setTodo] = React.useState("");
  const [todoList, setTodoList] = React.useState<string[]>([]);
  const [editTodoIdx, setEditTodoIdx] = React.useState<number | null>();
  const [doneMap, setDoneMap] = React.useState<typeof CustomMap>({});
  
  useEffect(() => {
    const eListStr: string | null = localStorage.getItem('todoList') ?? "";
    const existingList: string[] = eListStr?.length > 0 ? JSON.parse(eListStr): [];
    setTodoList(existingList);
    let doneMap: typeof CustomMap; 
    if(localStorage.getItem('doneMap')) {
      doneMap = JSON.parse(localStorage.getItem('doneMap') ?? "");
    } else {
      doneMap = {};
    }
    setDoneMap(doneMap);
  }, []);
  
  const getExistingList = (): string[] => {
    const eListStr: string | null = localStorage.getItem('todoList') ?? "";
    const existingList: string[] = eListStr?.length > 0 ? JSON.parse(eListStr): [];    
    return existingList;
  };
  
  const saveTodoList = (toSave: string[]) => {
    localStorage.setItem('todoList', JSON.stringify(toSave));
    setTodoList(toSave);
  }
  
  const enterTodo: MouseEventHandler<Element> = () => {
    const existingList = getExistingList();
    console.log(todo);
    if (editTodoIdx != null) {
      existingList[editTodoIdx] = todo;
    } else {
      existingList.push(todo);
    }
    localStorage.setItem("todoList", JSON.stringify(existingList));
    setTodoList(existingList);
    setTodo("");
    setEditTodoIdx(null);
  };
  
  const editTodo: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    let temp = btn.target as HTMLTextAreaElement;
    const existingList = getExistingList();
    const nBeingEdited = Number(temp.value);
    const todo = existingList[Number(temp.value)];
    setTodo(todo);
    setEditTodoIdx(nBeingEdited);
  }
  const handleChange: ChangeEventHandler<Element> = (event: ChangeEvent) => {
    var elem = event.target as HTMLTextAreaElement;

    setTodo(elem.value);
  }
  const saveDoneTodo = (idxNoStr: string) => {
    const doneMap: typeof CustomMap = JSON.parse(localStorage.getItem('doneMap') ?? "{}");
    const idx = Number(idxNoStr);
    doneMap[idx] = !doneMap[idx];
    setDoneMap(doneMap);
    localStorage.setItem('doneMap', JSON.stringify(doneMap));

  }
  const markDone: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    const idxNoStr = (btn.target as HTMLTextAreaElement).value;
    saveDoneTodo(idxNoStr);
  }
  const markDoneChange: ChangeEventHandler<Element> = (event: ChangeEvent) => {
    var elem = event.target as HTMLTextAreaElement;
    saveDoneTodo(elem.value);
  }
  const deleteTodo: MouseEventHandler<Element> = (btn: React.MouseEvent<HTMLElement>) => {
    const idxNoStr = (btn.target as HTMLTextAreaElement).value;
    const idx = Number(idxNoStr);
    const existingList = getExistingList();
    existingList.splice(idx, 1);
    saveTodoList(existingList);
  }
  return (
    <div className="App">
      <header>
        <h2> React todo list with Typescript</h2>
      </header>
      <div>
        <div>
          <div className='TodoArea'>
            <div>
              <input type='text' placeholder='enter todo' onChange={handleChange} value={todo} />
              <button onClick={enterTodo}> Save</button>  
            </div>
            <ul>
              {todoList.map( (t:string, idx: number) => (
                <div className='Todo' key={idx}>
                  {doneMap[idx] ? (
                    <input type='checkbox' checked value={""+idx} onChange={markDoneChange} />
                  ): (
                    <input type='checkbox' value={""+idx} onChange={markDoneChange} />
                  )}
                  <li> {t} </li> 
                  <p style={{marginLeft: 10}}>
                    <button value={""+idx} onClick={editTodo}> Edit</button>
                    <button style={{marginLeft: 10}} value={""+idx} onClick={deleteTodo}> Delete</button>
                    <button style={{marginLeft: 10}} value={""+idx} onClick={markDone}> Done</button>
                  </p>
                </div>
              ))}
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

You can find the full source code for this project on Github.

Summary

It is important to understand that what separates Typescript and Javascript is the static typing. Coming from a Java background I love static typing and I think it is great that we need to know the MouseEvent<HTMLElement> that are triggered.

On the flip side, coming from a Java background, this also reminds me a little bit of Java swing 🙂 Anyone remember that?

If you find any of my posts useful and want to support me, you can buy me a coffee 🙂

https://www.buymeacoffee.com/bhumansoni

Have a read of some of my other posts,

What is Javascript event loop? – My Day To-Do (mydaytodo.com)

How to build a game using Vanilla Javascript – My Day To-Do (mydaytodo.com)

Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)

Java Spring Boot & Vanilla Javascript solution – My Day To-Do (mydaytodo.com)

Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)

While you are here, maybe try one of my apps for the iPhone.

New tab (mydaytodo.com)

Apps – My Day To-Do (mydaytodo.com)


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *