DEV Community

Cover image for Beginner TypeScript: Vanilla&React Comparison
yoriss67
yoriss67

Posted on

Beginner TypeScript: Vanilla&React Comparison

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 ②プログラミングチュートリアル.

Image description

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)
}

Enter fullscreen mode Exit fullscreen mode

② 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;
Enter fullscreen mode Exit fullscreen mode

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) return is 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 of input is either null, 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}
              />

Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

🌸ChatGPT says

  • 🦥id: This is the argument to the handleEdit function. When the handleEdit function is called in the onChange event handler, the id property of the current Todo object is passed as this argument.
  • 🐼id: This is the id property of a Todo object. In the handleEdit function, this id is compared with the 🦥id argument to find the Todo object that needs to be edited.

So, when handleEdit is called with todo.🦥id and e.target.value, it maps over the todos array to create a new array. For each Todo object in the new array, if the id property of the Todo object (todo.🐼id) matches the 🦥id argument, it updates the inputValue property of the Todo object to e.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 newTodos is 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 the inputValue of a Todo, instead of directly modifying it like this:

// Don't do this!
todos[0].inputValue = "new value";
Enter fullscreen mode Exit fullscreen mode

You should create a new array of Todos, create a new Todo object 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);
Enter fullscreen mode Exit fullscreen mode

So, newTodos is used to create a new array with the changes applied, without directly modifying the original todos array. 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🏋️🏋️‍♀️🏋️‍♂️

Latest comments (2)

Collapse
 
hartley94 profile image
Martin Thuo

Learning continues 👏.

Collapse
 
yoriss67 profile image
yoriss67

Thanks!😆