Day29 :TypeScript Learning : React + TypeScript Todo App Part2
Continuing with the Todo app part 2, I will document the issues encountered during the implementation.
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,
StyledTodowas originally intended to directly useprops.completed, but it threw an error. It needs to be defined asstyled.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 tome(margin-end), andmr(margin-right) has changed toms(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:
onCreatealso needs to be typed; the callback function must have a defined type.- In
handleSubmit, I initially thought I could just usee, but it didn't work; I needed to define the event type. You can refer to React's Event System. TheonSubmitevent belongs to FormEvent, so I definedeaccordingly. There are more event classifications than I expected, and I suddenly feel that JavaScript is much easier since you can just usee.
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:
initialTodosalso needs to be typed since it is an array; we can give it the typeTodo[].AddTodo,ToggleTodo, andDeleteTodocan 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/
Related Posts
Day30 :I finally finished!
I finally finished! Recently, I went to draw, and here is a drawing of "The Little Prince and the Fox." My favorite part of "The Little Prince" is their story. Drawing is really therapeutic, just like the feeling of finishing this challenge.
Day28 :TypeScript Learning : React + TypeScript Todo App Part1
After taking so many notes, it's finally time to implement a simple to-do app and document the issues encountered during the implementation.
Day27 :TypeScript Learning : Module
Before ES6, the common module concept was CommonJS, using require to import modules and exports to export modules.