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.
0 Comments