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:

  1. Use TypeScript effectively for React components, state, and props.
  2. Build modular components for our TodoList app.
  3. Understand Context and Custom Hooks for 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:

  • TodoItemProps interface defines the types for props.
  • onDelete is a function type that accepts an id.

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-react

3.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

  1. Create a TaskCheckbox component using Radix UI for your own project.
  2. Use the Context API to manage todos globally, and add a function to toggle the completion status.
  3. 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.

Copyright © Big Poppa Code & Progress and Fortune LLC 2025