I started TypeScript as many YouTubers and companies say it is in demand these days.
Since I can understand English and Japanese, I used resources from both languages.
So, in this post, I make a comparison to embark on a new journey of TypeScript as an absolute beginner🔰🛳️
Videos
I used two YouTube videos, the first is WebDevSimplified.
The second is プログラミングチュートリアル.
Although they have different approaches, they have the same "To-do List" project.
So I tried if I could understand their differences and functionality.
I really struggled to understand them by placing them on screen.
The left is ①WebDevSimplified and the right is ②プログラミングチュートリアル.
Source code
① https://github.com/WebDevSimplified/typescript-todo-list/tree/main
import { v4 as uuidV4 } from "uuid"
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")
const tasks: Task[] = loadTasks()
tasks.forEach(addListItem)
form?.addEventListener("submit", e => {
e.preventDefault()
if (input?.value == "" || input?.value == null) return
const newTask: Task = {
id: uuidV4(),
title: input.value,
completed: false,
createdAt: new Date(),
}
tasks.push(newTask)
saveTasks()
addListItem(newTask)
input.value = ""
})
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
label.append(checkbox, task.title)
item.append(label)
list?.append(item)
}
function saveTasks() {
localStorage.setItem("TASKS", JSON.stringify(tasks))
}
function loadTasks(): Task[] {
const taskJSON = localStorage.getItem("TASKS")
if (taskJSON == null) return []
return JSON.parse(taskJSON)
}
② https://github.com/Shin-sibainu/todolist-react-typescript
import React, { useState } from "react";
import "./App.css";
function App() {
const [inputText, setInputText] = useState("");
//todo配列オブジェクトの更新用に用意。プロパティはinputValue, id, checkedの3つを更新する。
const [todos, setTodos] = useState<Todo[]>([]);
type Todo = {
inputValue: string;
id: number; //keyを指定するため
checked: boolean;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
setInputText(e.target.value);
};
const handleSubmit = (e: { preventDefault: () => void }) => {
e.preventDefault();
if (!inputText) {
return;
}
//新しいTodo作成
const newTodo: Todo = {
inputValue: inputText,
id: todos.length,
checked: false,
};
setTodos([newTodo, ...todos]);
console.log(inputText);
setInputText(inputText);
};
//todo編集 🤔①
const handleEdit = (id: number, inputValue: string) => {
/* ディープコピー(完全にコピーされた別の配列)に変えよう(ミューテートから守るため) */ 🤔②
const deepCopy = todos.map((todo) => ({ ...todo }));
console.log(deepCopy);
🤔③
const newTodos = deepCopy.map((todo) => {
if (todo.id === id) {
todo.inputValue = inputValue;
}
return todo;
});
// /* シャローコピー */
// const newTodos = todos.map((todo) => {
// if (todo.id === id) {
// todo.inputValue = inputValue;
// }
// return todo;
// });
setTodos(newTodos);
};
//完了未完了
const handleChecked = (id: number, checked: boolean) => {
/* ディープコピー(完全にコピーされた別の配列)に変えよう(ミューテートから守るため) */
const deepCopy = todos.map((todo) => ({ ...todo }));
// console.log(deepCopy);
const newTodos = deepCopy.map((todo) => {
if (todo.id === id) {
//反転
todo.checked = !checked;
}
return todo;
});
setTodos(newTodos);
};
//削除
const handleDelete = (id: number) => {
//idが正しくないのは残す。正しいと消す。
const newTodos = todos.filter((todo) => todo.id !== id);
setTodos(newTodos);
};
return (
<div className="App">
<div>
<h2>Todoリスト with Typescript</h2>
<form onSubmit={(e) => handleSubmit(e)}>
<input
type="text"
onChange={(e) => handleChange(e)}
className="inputText"
/>
<input type="submit" value="作成" className="submitButton" />
</form>
{/* タスク設定が完了したら */}
<ul className="todoList">
{todos.map((todo) => (
<li key={todo.id}>
<input
type="text"
value={todo.inputValue}
onChange={(e) => handleEdit(todo.id, e.target.value)}
disabled={todo.checked}
/>
<input
type="checkbox"
checked={todo.checked}
onChange={() => handleChecked(todo.id, todo.checked)}
/>
<button onClick={() => handleDelete(todo.id)}>消</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default App;
Thx ChatGPT!!!😭😭
I got ChatGPT to make a table to compare them.
| Aspect | Folder 1 (WebDevSimplified) | Folder 2 (React App) |
|---|---|---|
| Language | JavaScript/TypeScript | TypeScript |
| UI Library/Framework | None | React |
| State Management | Local variables and local storage | React useState hook |
| Type Definition | The Task type is defined with a type alias and used immediately. |
The Todo type is defined with a type alias inside the component function. |
| Form Handling | Uses traditional form and input handling with DOM APIs. | Uses React's controlled components pattern with useState. |
| Task/Todo Creation | Creates a new task object and pushes it to the tasks array. | Creates a new todo object and uses the setTodos function to update state. |
| Task/Todo Editing | Checkbox listener directly modifies the task's completed property. |
Uses a handleEdit function that creates a new array with the edited todo. |
| Task/Todo Completion | Checkbox listener directly modifies the task's completed property and saves to local storage. |
Uses a handleChecked function that creates a new array with the toggled checked property of the todo. |
| Task/Todo Deletion | Not implemented. | Uses a handleDelete function that filters the todos array to exclude the deleted one. |
| Task/Todo Rendering | Uses the addListItem function to create and append HTML elements. |
Uses JSX to map over the todos array and render list items. |
| Data Persistence | Uses localStorage to persist tasks. |
Does not persist todos beyond the current session. |
Where I didn't understand
🤔① What is “?” and why there’s nothing after return ?
On line 19 of WebDevSimplified,
if (input?.value == "" || input?.value == null) return
As for ?
🙋♂️WebDevSimplified says
that's because when we do our query selector for our input right here
it's returning to us something that is either an html input element or something that is null.
Because what if nothing with this id exists?
Well, we need to take that into account typescript says hey this thing could be null so you need to take into account that it could be null.
But I couldn't get it well. So I asked ChatGPT.
🌸ChatGPT says
Optional chaining: The video introduces optional chaining (?.) as a way to handle the possibility of null. Optional chaining is a way to tell TypeScript: "Hey, try to access this property or method, but if the object is null or undefined, just give me undefined immediately and don't throw an error." This is what the input?.value does in your code.
As for return
🌸ChatGPT says
The line
if (input?.value == "" || input?.value == null) returnis using what's known as an "early return" or a "guard clause". This is a common pattern in programming to stop the execution of a function if certain conditions are met.
In this case, the function is stopped (i.e., it "returns") if the value ofinputis eithernull,undefined, or an empty string (""). This is to prevent the creation of a new task when the input is empty or invalid.
🙋♀️So to summarize with my words, since TypeScript is more strict than normal js, we can let TypeScript think there is a possibility that the input might be null or undefined. If it is the case, TypeScript should return undefined immediately and do nothing after that.
🤔② Which id relates to which id in プログラミングチュートリアル?
On around line 41 of プログラミングチュートリアル,
const handleEdit = (id: number, inputValue: string) => {
/* ディープコピー(完全にコピーされた別の配列)に変えよう(ミューテートから守るため) */
const deepCopy = todos.map((todo) => ({ ...todo }));
console.log(deepCopy);
const newTodos = deepCopy.map((todo) => {
if (todo.id === id) {
todo.inputValue = inputValue;
}
return todo;
});
~~~~~~
<input
type="text"
value={todo.inputValue}
onChange={(e) => handleEdit(todo.id, e.target.value)}
disabled={todo.checked}
/>
I got confused how to understand which id corresponds to which.😂So I asked ChatGPT to put emojis.
// Define the Todo type
type Todo = {
inputValue: string;
id: number; // This id is the original source of both 🦥id and 🐼id.
checked: boolean;
};
// Define the handleEdit function
const handleEdit = (🦥id: number, inputValue: string) => {
const deepCopy = todos.map((todo) => ({ ...todo }));
const newTodos = deepCopy.map((todo) => {
if (todo.🐼id === 🦥id) { // Compare the 'id' property of a Todo object to the 'id' argument of the function
todo.inputValue = inputValue;
}
return todo;
});
setTodos(newTodos);
};
// Call the handleEdit function in the JSX
<input
type="text"
value={todo.inputValue}
onChange={(e) => handleEdit(todo.🦥id, e.target.value)} // Pass the 'id' property of the current Todo object as the first argument to the function
disabled={todo.checked}
/>
🌸ChatGPT says
- 🦥id: This is the argument to the
handleEditfunction. When thehandleEditfunction is called in theonChangeevent handler, theidproperty of the currentTodoobject is passed as this argument.- 🐼id: This is the
idproperty of aTodoobject. In thehandleEditfunction, thisidis compared with the🦥idargument to find theTodoobject that needs to be edited.So, when
handleEditis called withtodo.🦥idande.target.value, it maps over thetodosarray to create a new array. For eachTodoobject in the new array, if theidproperty of theTodoobject (todo.🐼id) matches the🦥idargument, it updates theinputValueproperty of theTodoobject toe.target.value.
🤔③ What is deep / shallow copy in プログラミングチュートリアル?
Since it was the first time, I didn't understand general concepts in programming.
🌸ChatGPT says
- Shallow copy: When you create a shallow copy of an object, you're creating a new object with the same properties, but the property values are not duplicated. This means that if a property value is an object or an array, the copy and the original will share the same reference to that object or array. If you modify the object or array through one of the references, the change will be visible when you access it through the other reference.
- Deep copy: When you create a deep copy of an object, you're creating a new object with the same properties, and all the property values are also duplicated. This means that the copy and the original have completely separate copies of all data, including nested objects and arrays. Changes to one will not affect the other.
🤔④ Why do we need to make const newTodos in プログラミングチュートリアル?
🌸ChatGPT says
The primary reason for creating
newTodosis to maintain the immutability of React's state.
In React, you should not directly modify state. Instead of altering or updating an existing state object directly, you should generate a new state object and set that. This is a crucial part of how React detects changes to state and re-renders appropriately.
For instance, when you want to change theinputValueof aTodo, instead of directly modifying it like this:
// Don't do this!
todos[0].inputValue = "new value";
You should create a new array of
Todos, create a newTodoobject with the changes, and then set that as the state:
const newTodos = todos.map((todo) => {
if (todo.id === idToEdit) {
return { ...todo, inputValue: "new value" };
} else {
return todo;
}
});
setTodos(newTodos);
So,
newTodosis used to create a new array with the changes applied, without directly modifying the originaltodosarray. This maintains the immutability of React's state, allowing React to properly detect changes to state and re-render.
Thank you for reading!
I had a lot to learn, not only TypeScript, but also React and JavaScript concepts. I am never confident that I perfectly understand each code, but I'm happy that I made a progress🏋️🏋️♀️🏋️♂️

Top comments (2)
Learning continues 👏.
Thanks!😆