Happylab Logo
Published on

Day29 :TypeScript Learning : React + TypeScript Todo App Part2

Authors

Day29 :TypeScript Learning : React + TypeScript Todo App Part2

Continuing with the Todo app part 2, I will document the issues encountered during the implementation.

If there are any mistakes, please feel free to leave comments. Thank you!


TodoItem Component

Add src/components/todos/TodoItem.tsx:

//@file: src/components/todos/TodoItem.tsx
import styled from "styled-components";

// Define Props interface, todo must conform to the Todo shape
interface Props {
    todo: Todo; // Defined globally in types.d.ts
    index: number;
    onToggleTodo: ToggleTodo;
    onDeleteTodo: DeleteTodo;
}

// Define completed style, the completed return value will be of boolean type; if true, it will be strikethrough, otherwise none
const StyledTodo = styled.span<{ completed: boolean }>`
    text-decoration: ${(props) => (props.completed ? "line-through" : "none")};
`;

// React.FC<Props> defines this FunctionComponent as Props generic
const TodoItem: React.FC<Props> = ({
    index,
    todo,
    onToggleTodo,
    onDeleteTodo,
}) => {
    return (
        <div className="form-check border border-bottom-secondary rounded py-3 m-0 d-flex justify-content-between align-items-center">
            <div>
                <input
                    className="ms-1 me-3 form-check-input"
                    type="checkbox"
                    checked={todo.complete}
                    onClick={() => {
                        onToggleTodo && onToggleTodo(index);
                    }}
                />
                <StyledTodo className="todo" completed={todo.complete}>
                    {todo.text}
                </StyledTodo>
            </div>

            <button
                className="btn btn-outline-danger me-3"
                onClick={() => onDeleteTodo && onDeleteTodo(index)}
            >
                X
            </button>
        </div>
    );
};

export default TodoItem;

I encountered some issues:

  • React.FC<Props> is a generic used in TypeScript, where FC stands for FunctionComponent.
  • React.FC<Props> will pass the defined property types from Props as parameters, similar to how we write functions with parameters.
  • I find TypeScript to be quite strict; for example, StyledTodo was originally intended to directly use props.completed, but it threw an error. It needs to be defined as styled.span<{ completed: boolean }> where the passed value is of boolean type.
  • Also, I realized that I hadn't used Bootstrap in a while; in v5, the original ml (margin-left) has changed to me (margin-end), and mr (margin-right) has changed to ms (margin-start).

AddTodoInput Component

Add src/components/todos/AddTodoInput.tsx:

//@file: src/components/todos/AddTodoInput.tsx

import React, { FormEvent, useState } from "react";

interface Props {
    onCreate: IAddTodo;
}

const AddTodoInput: React.FC<Props> = ({ onCreate }) => {
    const [text, setText] = useState("");

    const handleSubmit = (e: FormEvent) => {
        e.preventDefault();
        onCreate && onCreate(text);
        setText("");
    };

    return (
        <form className="input-group mb-5" onSubmit={handleSubmit}>
            <input
                className="form-control border-primary"
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
            />
            <button
                className="btn btn-primary"
                type="submit"
                onClick={handleSubmit}
                disabled={!text}
            >
                Add Todo
            </button>
        </form>
    );
};

export default AddTodoInput;

I encountered some issues:

  • onCreate also needs to be typed; the callback function must have a defined type.
  • In handleSubmit, I initially thought I could just use e, but it didn't work; I needed to define the event type. You can refer to React's Event System. The onSubmit event belongs to FormEvent, so I defined e accordingly. There are more event classifications than I expected, and I suddenly feel that JavaScript is much easier since you can just use e.

Home Page

Add src/pages/Home.tsx:

//@file: src/pages/Home.tsx
import { useState } from "react";
import AddTodoInput from "../components/todos/AddTodoInput";
import TodoItem from "../components/todos/TodoItem";
import { v4 as uuid } from "uuid";

const initialTodos: Todo[] = [
    {
        text: "walk the dog",
        complete: true,
    },
    {
        text: "learn TypeScript",
        complete: false,
    },
];

const Home = () => {
    const [todos, setTodos] = useState(initialTodos);

    const addTodo: AddTodo = (text: string) => {
        const newTodo = { text, complete: false };
        setTodos([...todos, newTodo]);
    };

    const toggleTodo: ToggleTodo = (index: number) => {
        const newTodos: Todo[] = [...todos];
        newTodos[index].complete = !newTodos[index].complete;
        setTodos(newTodos);
    };

    const deleteTodo: DeleteTodo = (index: number) => {
        setTodos([...todos.slice(0, index), ...todos.slice(index + 1)]);
    };

    return (
        <main className="pt-5 mx-auto">
            <div className="container">
                <AddTodoInput onCreate={addTodo} />
                {todos.map((todo, index) => {
                    return (
                        <TodoItem
                            todo={todo}
                            key={uuid()}
                            index={index}
                            onToggleTodo={toggleTodo}
                            onDeleteTodo={deleteTodo}
                        />
                    );
                })}
            </div>
        </main>
    );
};

export default Home;

I encountered some issues:

  • initialTodos also needs to be typed since it is an array; we can give it the type Todo[].
  • AddTodo, ToggleTodo, and DeleteTodo can be defined in types.d.ts, which is cleaner than defining them inside the function.

App.tsx

Don't forget to update this as well:

import Home from "./pages/Home";

const App = () => {
    return <Home />;
};

export default App;

Completed!

This completes the simple Todo App, and you can also refer to it on GitHub.


Day29 done. Tomorrow is the last day~~ I'm a bit emotional!!!


References

https://typeofnan.dev/your-first-react-typescript-project-todo-app/