Lesson 4: Deep Dive into TypeScript and TSX for Larger Applications
Goal
Understand how TypeScript integrates into React (TSX) in scalable and reusable components. By the end of this lesson, students will be able to:
- Use TypeScript effectively for React components, state, and props.
- Build modular components for our TodoList app.
- Understand
ContextandCustom Hooksfor better code organization.
1. Learning Objectives
By the end of this lesson, students will:
- Define reusable TypeScript interfaces for components.
- Understand props and state typing in React.
- Create components for the TodoList app using Radix UI and Phosphor Icons.
- Explore Context API and Custom Hooks for managing global state.
2. TypeScript and TSX in React: Key Concepts
2.1. Functional Components with Props
Props define what data a React component receives. TypeScript enforces type safety.
Example: TodoItem Component
import React from 'react';
interface TodoItemProps {
id: number;
title: string;
completed: boolean;
onDelete: (id: number) => void; // Function type
}
const TodoItem: React.FC<TodoItemProps> = ({ id, title, completed, onDelete }) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
<h3>{title}</h3>
<p>{completed ? 'Completed' : 'Not Completed'}</p>
</div>
<button onClick={() => onDelete(id)}>Delete</button>
</div>
);
};
export default TodoItem;Key Notes:
TodoItemPropsinterface defines the types for props.onDeleteis a function type that accepts anid.
2.2. Typing State with useState
When using useState, specify the type of the state.
Example: Managing a List of Todos
import React, { useState } from 'react';
import TodoItem from './TodoItem';
interface Todo {
id: number;
title: string;
completed: boolean;
}
const TodoList: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, title: 'Learn TypeScript', completed: false },
{ id: 2, title: 'Learn React', completed: true },
]);
const deleteTodo = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div>
<h1>My Todo List</h1>
{todos.map((todo) => (
<TodoItem
key={todo.id}
id={todo.id}
title={todo.title}
completed={todo.completed}
onDelete={deleteTodo}
/>
))}
</div>
);
};
export default TodoList;3. Building the TodoList App: UI with Radix UI & Phosphor Icons
3.1. Setting Up Radix UI and Phosphor Icons
Installation:
npm install @radix-ui/react-checkbox phosphor-react3.2. Creating a Reusable Checkbox Component
We’ll use Radix UI to create an accessible checkbox.
Checkbox Component:
import * as Checkbox from '@radix-ui/react-checkbox';
import { Check } from 'phosphor-react';
interface TaskCheckboxProps {
label: string;
checked: boolean;
onChange: (checked: boolean) => void;
}
const TaskCheckbox: React.FC<TaskCheckboxProps> = ({ label, checked, onChange }) => {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<Checkbox.Root
checked={checked}
onCheckedChange={(checked) => onChange(!!checked)}
style={{ width: '20px', height: '20px', border: '1px solid black' }}
>
<Checkbox.Indicator>
<Check />
</Checkbox.Indicator>
</Checkbox.Root>
<label>{label}</label>
</div>
);
};
export default TaskCheckbox;3.3. Integrating the Checkbox in TodoItem
import React from 'react';
import TaskCheckbox from './TaskCheckbox';
interface TodoItemProps {
id: number;
title: string;
completed: boolean;
onDelete: (id: number) => void;
onToggle: (id: number) => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ id, title, completed, onDelete, onToggle }) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<TaskCheckbox
label={title}
checked={completed}
onChange={() => onToggle(id)}
/>
<button onClick={() => onDelete(id)}>Delete</button>
</div>
);
};
export default TodoItem;4. Using Context for Global State
We will use React’s Context API to manage todos globally.
4.1. Create Todo Context
import React, { createContext, useContext, useState } from 'react';
interface Todo {
id: number;
title: string;
completed: boolean;
}
interface TodoContextType {
todos: Todo[];
addTodo: (title: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
}
const TodoContext = createContext<TodoContextType | undefined>(undefined);
export const TodoProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [todos, setTodos] = useState<Todo[]>([]);
const addTodo = (title: string) => {
setTodos([...todos, { id: Date.now(), title, completed: false }]);
};
const toggleTodo = (id: number) => {
setTodos(todos.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo)));
};
const deleteTodo = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<TodoContext.Provider value={{ todos, addTodo, toggleTodo, deleteTodo }}>
{children}
</TodoContext.Provider>
);
};
export const useTodos = () => {
const context = useContext(TodoContext);
if (!context) throw new Error('useTodos must be used within a TodoProvider');
return context;
};4.2. Using the Context in App.tsx
import React from 'react';
import { TodoProvider, useTodos } from './TodoContext';
import TodoItem from './TodoItem';
const TodoList: React.FC = () => {
const { todos, addTodo, toggleTodo, deleteTodo } = useTodos();
return (
<div>
<h1>Todo List</h1>
<button onClick={() => addTodo('New Task')}>Add Task</button>
{todos.map((todo) => (
<TodoItem
key={todo.id}
id={todo.id}
title={todo.title}
completed={todo.completed}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</div>
);
};
const App: React.FC = () => {
return (
<TodoProvider>
<TodoList />
</TodoProvider>
);
};
export default App;5. Homework Assignment
- Create a TaskCheckbox component using Radix UI for your own project.
- Use the Context API to manage todos globally, and add a function to toggle the completion status.
- Build a page displaying a list of tasks using the new checkbox component.
Next Lesson Preview: Lesson 5
In Lesson 5, we will:
- Start building the backend by creating an Express server and MongoDB integration.
- Test the backend with Jest, Supertest, and MongoMemoryServer.
