0% found this document useful (0 votes)
2 views88 pages

Type Script

The TypeScript tutorial covers the fundamentals of TypeScript, a strongly typed superset of JavaScript that enhances development with better tooling and type safety. It includes installation instructions, project structure, key concepts like types, arrays, objects, and functions, as well as best practices for using TypeScript effectively. The course is designed for beginners and spans over 8 hours of content, providing a comprehensive introduction to TypeScript programming.

Uploaded by

mandarp1110
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views88 pages

Type Script

The TypeScript tutorial covers the fundamentals of TypeScript, a strongly typed superset of JavaScript that enhances development with better tooling and type safety. It includes installation instructions, project structure, key concepts like types, arrays, objects, and functions, as well as best practices for using TypeScript effectively. The course is designed for beginners and spans over 8 hours of content, providing a comprehensive introduction to TypeScript programming.

Uploaded by

mandarp1110
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Typescript

TypeScript Tutorial Notes

Course: TypeScript for Beginners


Instructor: Dave Gray
Total Duration: 8+ hours (17 tutorials)

Chapter 1: Start Here

What is TypeScript?

TypeScript is JavaScript with syntax for types. It's a strongly typed programming language that builds on
JavaScript, providing better tooling at any scale.

Key Points:

TypeScript is a developer tool that helps write better JavaScript

Created by Microsoft (Anders Heisberg also created C#)

TypeScript compiles to JavaScript


Ranked 5th in Stack Overflow's most popular programming languages

Prerequisites

Before learning TypeScript, you should know JavaScript fundamentals, as TypeScript is a superset of
JavaScript and extends it.

Required Tools

1. Visual Studio Code - [Link]


2. [Link] (includes npm) - [Link]

Setup & Installation

# Install TypeScript globally


npm install typescript -g

# Initialize TypeScript config


tsc --init

# Compile a TypeScript file


tsc [Link]
# Watch mode for a single file
tsc [Link] -w

# Watch all files (with config)


tsc -w

Project Structure

project/
├── src/ # TypeScript source files
├── build/ # Compiled output
│ ├── [Link]
│ ├── css/
│ └── js/ # Compiled JavaScript
└── [Link] # TypeScript configuration

TypeScript Configuration ([Link])

Important settings:

{
"compilerOptions": {
"target": "ES2016", // JavaScript version to compile to
"rootDir": "./src", // Source directory
"outDir": "./build/js", // Output directory
"noEmitOnError": true // Don't compile if errors exist
},
"include": ["src"] // Only compile files in src
}

Compilation Behavior

TypeScript compiles to JavaScript even with errors (by default)


Use noEmitOnError: true to prevent compilation when errors exist

Can also use flag: tsc --noEmitOnError -w

ES5 uses var , ES2016+ uses let/const

Key Concepts

Strongly Typed vs Loosely Typed:

Strongly typed: Requires type specification (TypeScript)

Loosely typed: No type specification required (JavaScript)

Static vs Dynamic Typing:

Static: Types checked at compile time (TypeScript)


Dynamic: Types checked at runtime (JavaScript)

TypeScript Benefits:

Self-documenting code
Catches errors during development

Great for teams


Better tooling and IntelliSense

Chapter 2: Basic Types

Terminology

Inference: TypeScript figures out the type automatically

Implicit: Type is not explicitly stated but inferred


Explicit: Type is directly declared

Basic Type Syntax

// Implicit (TypeScript infers the type)


let myName = "Dave"; // inferred as string

// Explicit (TypeScript told the type)


let myName: string = "Dave";

// Variable without assignment


let myName: string;
myName = "Dave"; // Must be a string

Primitive Types

// String
let myName: string = "Dave";

// Number
let meaningOfLife: number = 42;

// Boolean
let isLoading: boolean = true;

// Any (allows any type - use sparingly!)


let album: any = "Van Halen";
album = 1984; // OK
album = true; // OK
Union Types

Union types allow a variable to be one of several types:

let album: string | number = "Van Halen";


album = 1984; // OK
album = true; // Error!

let postId: string | number;


let isActive: number | boolean;

// Union types can have more than 2 types


let value: string | number | boolean;

Regular Expression Type

let re: RegExp = /\w+/g;

Functions and Types

// Function with typed parameters


function sum(a: number, b: number) {
return a + b; // TypeScript infers return type: number
}

// Mixed types
function concat(a: number, b: string) {
return a + b; // TypeScript infers return type: string
}

Type Safety Benefits

TypeScript prevents common JavaScript errors:

let myName: string = "Dave";


myName = 42; // Error: Type 'number' is not assignable to type 'string'

// Variable reassignment is allowed with same type


myName = "John"; // OK

The any Type

The any type defeats TypeScript's purpose but has valid use cases:

let album: any;


album = "Van Halen"; // OK
album = 1984; // OK
album = true; // OK

When to use any :

When you're unsure what type you'll receive


When migrating JavaScript to TypeScript

Use sparingly - it defeats type safety

Important Notes

Valid JavaScript is valid TypeScript

TypeScript compiler may warn but still compiles


Inference is OK, but explicit types are clearer

Union types provide flexibility with safety

Chapter 3: Arrays & Objects

Arrays
Basic Array Types

// String array (inferred)


let stringArr = ["hey", "hello", "Dave"];
// Type: string[]

// Union type array (inferred)


let guitars = ["Strat", "Les Paul", 5150];
// Type: (string | number)[]

// Multiple types (inferred)


let mixedData = ["EVH", 1984, true];
// Type: (string | number | boolean)[]

Explicit Array Types

// Empty array with explicit type


let bands: string[] = [];
[Link]("Van Halen"); // OK
[Link](42); // Error!

// Empty array without type becomes 'any'


let test = []; // Type: any[]

Array Operations
let stringArr = ["one", "hey", "Dave"];

// Reassignment
stringArr[0] = "John"; // OK
stringArr[0] = 42; // Error!

// Adding elements
[Link]("hey"); // OK
[Link](42); // Error!

// Union type arrays


let guitars: (string | number)[] = ["Strat", "Les Paul", 5150];
guitars[0] = 1984; // OK (can swap types)
[Link]("Jim"); // OK
[Link](true); // Error!

Array Assignment Rules

let stringArr: string[] = ["one", "two"];


let guitars: (string | number)[] = ["Strat", 5150];
let mixedData: (string | number | boolean)[] = ["EVH", 1984, true];

stringArr = guitars; // Error! (guitars accepts numbers)


guitars = stringArr; // OK (guitars accepts strings)
guitars = mixedData; // Error! (guitars doesn't accept boolean)
mixedData = guitars; // OK (mixedData accepts string & number)

Tuples

Tuples are arrays with fixed length and specific types in specific positions:

// Tuple definition
let myTuple: [string, number, boolean] = ["Dave", 42, true];

// Regular array (for comparison)


let mixed = ["John", 1, false]; // Type: (string | number | boolean)[]

// Key differences
mixed = myTuple; // OK (tuple can assign to array)
myTuple = mixed; // Error! (array might not have exactly 3 elements)

// Tuples enforce position types


myTuple[0] = "John"; // OK
myTuple[0] = 42; // Error! (position 0 must be string)
myTuple[3] = 42; // Error! (only 3 elements allowed)
Objects

Basic Object Types

// Simple object type annotation


let myObject: object;

// Arrays are also objects!


myObject = []; // OK
myObject = bands; // OK
myObject = {}; // OK

[Link](typeof myObject); // "object"

Object with Properties

// Object with inferred property types


const exampleObj = {
prop1: "Dave",
prop2: true
};
// TypeScript infers:
// prop1: string
// prop2: boolean

exampleObj.prop1 = "John"; // OK
exampleObj.prop1 = 42; // Error!
exampleObj.prop2 = false; // OK

Type Aliases for Objects

Using type keyword to define object structure:

type Guitarist = {
name: string;
active: boolean;
albums: (string | number)[];
};

let evh: Guitarist = {


name: "Eddie",
active: false,
albums: [1984, 5150, "OU812"]
};

let jp: Guitarist = {


name: "Jimmy",
active: true,
albums: ["I", "II", "IV"]
};

// Can reassign if both are same type


evh = jp; // OK (both are Guitarist type)

Optional Properties

type Guitarist = {
name: string;
active?: boolean; // Optional property
albums: (string | number)[];
};

// Now 'active' is optional


let jp: Guitarist = {
name: "Jimmy",
albums: ["I", "II", "IV"]
// No 'active' property - OK!
};

Type Narrowing

When a property is optional, TypeScript requires checking before use:

type Guitarist = {
name?: string;
active: boolean;
albums: (string | number)[];
};

function greetGuitarist(guitarist: Guitarist) {


// Error without narrowing:
// return `Hello ${[Link]()}!`;

// Correct with narrowing:


if ([Link]) {
return `Hello ${[Link]()}!`;
}
return "Hello!";
}

Interfaces vs Types

Both can define object shapes, but interfaces are commonly used for objects:
// Using 'type'
type Guitarist = {
name: string;
active: boolean;
albums: (string | number)[];
};

// Using 'interface' (works the same for basic cases)


interface Guitarist {
name: string;
active: boolean;
albums: (string | number)[];
}

When to use which:

Interface: Typically for class-like structures and objects

Type: For unions, primitives, and more complex types

For beginners: It's mostly preference for simple object definitions

Object Restrictions

type Guitarist = {
name: string;
active: boolean;
albums: (string | number)[];
};

let evh: Guitarist = {


name: "Eddie",
active: false,
albums: [1984, 5150]
};

// Cannot add properties not in type definition


[Link] = 40; // Error! Property 'years' doesn't exist

Enums

Enums are a TypeScript addition to JavaScript (not just a type-level feature):

// Basic enum
enum Grade {
U, // 0
D, // 1
C, // 2
B, // 3
A // 4
}

[Link](Grade.U); // 0
[Link](Grade.B); // 3
[Link](Grade.A); // 4

Custom Enum Values

// Start at 1 instead of 0
enum Grade {
U = 1, // 1
D, // 2
C, // 3
B, // 4
A // 5
}

[Link](Grade.U); // 1
[Link](Grade.B); // 4
[Link](Grade.A); // 5

Note: Unlike most TypeScript features, enums add something to JavaScript at runtime, not just compile
time.

Quick Reference

Type Annotations

// Variables
let name: string = "Dave";
let age: number = 42;
let isActive: boolean = true;
let data: any = "anything";

// Arrays
let names: string[] = ["Dave", "John"];
let mixed: (string | number)[] = ["Dave", 42];

// Tuples
let tuple: [string, number] = ["Dave", 42];

// Objects
let person: { name: string; age: number } = {
name: "Dave",
age: 42
};

// Type aliases
type Person = {
name: string;
age: number;
};

// Interfaces
interface Person {
name: string;
age: number;
}

// Enums
enum Status {
Active,
Inactive,
Pending
}

Common Patterns

// Optional properties
type User = {
name: string;
email?: string; // Optional
};

// Union types
let id: string | number;

// Type narrowing
if (typeof value === "string") {
// TypeScript knows 'value' is string here
}

// Function with types


function add(a: number, b: number): number {
return a + b;
}
Key Takeaways

1. TypeScript extends JavaScript - All valid JavaScript is valid TypeScript

2. Type inference is powerful - TypeScript can figure out types automatically

3. Explicit types are clearer - But inference works well too

4. Union types provide flexibility - Allow multiple types while maintaining safety

5. Tuples are strict arrays - Fixed length and specific position types

6. Objects need structure - Use type or interface to define shapes

7. Optional properties require narrowing - Check existence before use

8. Enums are runtime additions - Unlike most TypeScript features

Resources
Official TypeScript website: [Link]

Visual Studio Code: [Link]

[Link]: [Link]

Course GitHub resources: [link in video description]

Dave Gray's YouTube channel: @DaveGrayTeachesCode

Next: Chapter 4 and beyond will cover more advanced TypeScript concepts!

TypeScript Tutorial Notes - Part 2

Course: TypeScript for Beginners (Chapters 4-9)


Instructor: Dave Gray

Chapter 4: Functions

Type Aliases

Type aliases allow you to create custom names for any type, making code more readable and DRY
(Don't Repeat Yourself).

// Basic type alias


type StringOrNumber = string | number;

// Array type alias


type StringOrNumberArray = (string | number)[];

// Using aliases in other aliases


type UserId = StringOrNumber;

// Type aliases work with any TypeScript type


type Album = string | number;

Type vs Interface:

Type aliases: Can represent ANY TypeScript type (primitives, unions, etc.)

Interfaces: Best for objects and class-like structures

Cannot use interfaces for simple type aliases like union types

// This works with type


type PostId = string | number;

// This does NOT work with interface


interface PostId = string | number; // ❌ Error!

Literal Types

Literal types allow you to specify exact values a variable can have:

// Single literal (like const)


let myName: "Dave" = "Dave";
myName = "John"; // ❌ Error! Can only be "Dave"

// Useful with union types


type Username = "Dave" | "John" | "Amy";

let username: Username;


username = "Amy"; // ✅ OK
username = "Rachel"; // ❌ Error!

// Works with numbers too


type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

Benefits:

Restrict values to specific options

Great for configuration or state management

Keep code DRY when used in multiple places

TypeScript provides IntelliSense for literal options

Functions

Basic Function Typing


// Explicit parameter and return types
function add(a: number, b: number): number {
return a + b;
}

// Arrow function
const subtract = (c: number, d: number): number => {
return c - d;
};

Void Return Type

Functions that don't return a value use void :

function logMessage(message: any): void {


[Link](message);
}

Function Type Signatures

Using type aliases for function signatures:

// Type alias for function signature


type MathFunction = (a: number, b: number) => number;

// Using the type


let multiply: MathFunction = function(c, d) {
return c * d;
};

// TypeScript infers c and d are numbers

Interface alternative (less common for functions):

interface MathFunction {
(a: number, b: number): number;
}

Optional Parameters

function addAll(a: number, b: number, c?: number): number {


if (typeof c !== "undefined") {
return a + b + c;
}
return a + b;
}
addAll(2, 3); // ✅ OK - returns 5
addAll(2, 3, 2); // ✅ OK - returns 7

Rules:

Optional parameters must come last

Use type guards to check if optional parameter exists

Default Parameters

function sumAll(a: number = 10, b: number, c: number = 2): number {


return a + b + c;
}

sumAll(2, 3); // 2 + 3 + 2 = 7
sumAll(undefined, 3); // 10 + 3 + 2 = 15

Important notes:

Default params can be anywhere in the parameter list

To skip a default param, pass undefined

Cannot use default values in function type signatures

Rest Parameters

function total(a: number, ...nums: number[]): number {


return a + [Link]((prev, curr) => prev + curr, 0);
}

total(10, 2, 3); // 15
total(1, 2, 3); // 6

Rules:

Rest parameters must be last

Represented as an array inside the function

Don't use array brackets when calling

The never Type

The never type represents values that never occur:

// Function that throws error


function createError(errorMsg: string): never {
throw new Error(errorMsg);
}
// Endless loop
const infinite = (): never => {
let i: number = 1;
while (true) {
i++;
}
};

When never appears:

Functions that explicitly throw errors

Functions with infinite loops

Unreachable code

Useful with type guards:

function createNumberOrString(value: number | string): string {


if (typeof value === "string") {
return "string";
}
if (typeof value === "number") {
return "number";
}
// This should never happen
return createError("This should never happen!");
}

Custom Type Guards

const isNumber = (value: any): boolean => {


return typeof value === "number" ? true : false;
};

function numberOrString(value: number | string): string {


if (isNumber(value)) {
return "number";
}
if (typeof value === "string") {
return "string";
}
return createError("This should never happen!");
}

Chapter 5: Type Assertions (Type Casting)


What are Type Assertions?

Type assertions tell TypeScript you know more about a type than it does. Sometimes called "type
casting" (though not the same as runtime casting in other languages).

Basic Assertion Syntax

Two ways to assert types:

// 1. 'as' keyword (preferred, works in TSX)


let myValue = someValue as string;

// 2. Angle bracket syntax (doesn't work in React TSX files)


let myValue = <string>someValue;

Converting Specificity

type One = string;


type Two = string | number;
type Three = "hello";

let a: One = "hello";


let b = a as Two; // Less specific (string → string | number)
let c = a as Three; // More specific (string → "hello")

Practical Use: Narrowing Types

function addOrConcat(
a: number,
b: number,
c: "add" | "concat"
): number | string {
if (c === "add") return a + b;
return "" + a + b;
}

// Assertion tells TypeScript we know the return type


let myVal: string = addOrConcat(2, 2, "concat") as string;

// ⚠️ Be careful - TypeScript trusts you!


let nextVal: number = addOrConcat(2, 2, "concat") as number; // Wrong but no
error!

The unknown Type

unknown is safer than any - you must narrow it before use:


// Double assertion (force casting)
let value = 10 as unknown as string; // Overrides TypeScript

When to use: Rarely! Only when absolutely necessary.

DOM Assertions

TypeScript doesn't know about your HTML, so assertions are common with DOM:

// TypeScript infers: HTMLImageElement | null


const img = [Link]("img");

// TypeScript infers: Element | null


const myImg = [Link]("img");

// Assertion to specific type


const img = [Link]("img") as HTMLImageElement;

const myImg = [Link]("img") as HTMLImageElement;

Non-null Assertion

Use ! to tell TypeScript a value is not null:

const img = [Link]("img")!; // Not null


[Link]; // OK

// Or use 'as' assertion


const myImg = [Link]("img") as HTMLImageElement;
[Link]; // OK

Note: Non-null assertion is redundant when using as with specific type.

Common DOM Element Types

HTMLImageElement
HTMLSpanElement
HTMLDivElement
HTMLElement
Element

Practical Example: Copyright Year

// JavaScript that needs TypeScript refactoring


const year = [Link]("year");
const thisYear = new Date().getFullYear();
[Link]("datetime", thisYear);
[Link] = thisYear;

Problems:

1. year is possibly null

2. thisYear is number but needs to be string

TypeScript solution:

const year: HTMLSpanElement | null = [Link]("year") as


HTMLSpanElement;
const thisYear: string = new Date().getFullYear().toString();

if (year) {
[Link]("datetime", thisYear);
[Link] = thisYear;
}

Better solution with assertions:

const year = [Link]("year") as HTMLSpanElement;


const thisYear: string = new Date().getFullYear().toString();

[Link]("datetime", thisYear);
[Link] = thisYear;

Key Takeaways

Assertions tell TypeScript "trust me, I know better"

TypeScript can't always check assertions - mistakes are possible

Use as syntax (works in React), avoid <> syntax in TSX

Common with DOM manipulation

Use sparingly - defeats TypeScript's safety

Chapter 6: Classes

Basic Class Structure

class Coder {
name: string;
music: string;
age: number;
lang: string;
constructor(name: string, music: string, age: number, lang: string) {
[Link] = name;
[Link] = music;
[Link] = age;
[Link] = lang;
}
}

Important: Properties must be declared both in class body AND constructor parameters.

Visibility Modifiers (Data Modifiers)

Three visibility modifiers control access:

class Coder {
public name: string; // Accessible everywhere (default)
private age: number; // Only within this class
protected lang: string; // Within class and subclasses

constructor(
name: string,
age: number,
lang: string
) {
[Link] = name;
[Link] = age;
[Link] = lang;
}
}

Shorthand with Visibility Modifiers

Using visibility modifiers in constructor removes redundancy:

class Coder {
constructor(
public name: string,
public music: string,
private age: number,
protected lang: string = "TypeScript" // Default value
) {}
}

const dave = new Coder("Dave", "Rock", 42);

Benefits:
Less repetitive code
Automatically creates and assigns properties

Can combine with readonly

Readonly Properties

class Coder {
constructor(
public readonly name: string,
public music: string,
private age: number
) {}
}

const dave = new Coder("Dave", "Rock", 42);


[Link] = "John"; // ❌ Error! Readonly property
[Link] = "Jazz"; // ✅ OK

Methods

class Coder {
constructor(
public name: string,
private age: number
) {}

public getAge(): string {


return `Hello, I'm ${[Link]}`;
}
}

const dave = new Coder("Dave", 42);


[Link]([Link]()); // "Hello, I'm 42"
[Link]([Link]); // ❌ Error! Private property

Extending Classes (Inheritance)

class WebDev extends Coder {


constructor(
public computer: string,
name: string,
age: number
) {
super(name, age); // Must call super() first!
}
public getLang(): string {
return `I write ${[Link]}`; // Access protected property
}
}

const sarah = new WebDev("Mac", "Sarah", 25);

Rules:

super() must be called before accessing this

Private properties from parent are NOT accessible

Protected properties ARE accessible in subclasses

Implementing Interfaces

interface Musician {
name: string;
instrument: string;
play(action: string): string;
}

class Guitarist implements Musician {


name: string;
instrument: string;

constructor(name: string, instrument: string) {


[Link] = name;
[Link] = instrument;
}

play(action: string): string {


return `${[Link]} ${action} the ${[Link]}`;
}
}

const page = new Guitarist("Jimmy", "guitar");


[Link]([Link]("strums")); // "Jimmy strums the guitar"

Important: All properties and methods from interface must be implemented.

Static Members

Static members belong to the class itself, not instances:


class Peeps {
static count: number = 0;

static getCount(): number {


return [Link]; // Access with class name, not 'this'
}

public id: number;

constructor(public name: string) {


[Link] = ++[Link]; // Increment class-level count
}
}

const john = new Peeps("John");


const steve = new Peeps("Steve");
const amy = new Peeps("Amy");

[Link]([Link]); // 3
[Link]([Link]); // 1
[Link]([Link]); // 2
[Link]([Link]); // 3

Getters and Setters

class Bands {
private dataState: string[];

constructor() {
[Link] = [];
}

public get data(): string[] {


return [Link];
}

public set data(value: string[]) {


if ([Link](value) && [Link](el => typeof el === "string")) {
[Link] = value;
return;
}
throw new Error("Param is not an array of strings");
}
}
const myBands = new Bands();
[Link] = ["Neil Young", "Led Zep"];
[Link]([Link]); // ["Neil Young", "Led Zep"]

[Link] = [...[Link], "ZZ Top"];


[Link]([Link]); // ["Neil Young", "Led Zep", "ZZ Top"]

Important:

Getters can have return types

Setters CANNOT have return types (not even void )

Use getters/setters to add validation logic

Chapter 7: Index Signatures & keyof Assertions

What are Index Signatures?

Index signatures are useful when:

1. You don't know exact property names in advance

2. You need to access object properties dynamically

The Problem

interface TransactionObj {
pizza: number;
books: number;
job: number;
}

const todaysTransactions: TransactionObj = {


pizza: -10,
books: -5,
job: 50
};

// Static access - OK
[Link]([Link]); // -10

// Dynamic access - ERROR!


let prop: string = "pizza";
[Link](todaysTransactions[prop]); // ❌ Error!
Error: "Element implicitly has an 'any' type because expression of type 'string' can't be used to index
type 'TransactionObj'"

Index Signature Solution

interface TransactionObj {
[index: string]: number; // Index signature
}

// Now dynamic access works


let prop: string = "pizza";
[Link](todaysTransactions[prop]); // ✅ OK

// Loop access also works


for (const transaction in todaysTransactions) {
[Link](todaysTransactions[transaction]); // ✅ OK
}

Index Signature Syntax

interface MyInterface {
[index: string]: number; // or 'key' instead of 'index'
// Keys are strings, values are numbers
}

Key types allowed:

string

number

symbol

Template literal types

NOT allowed: boolean

With Specific Properties

You can combine index signatures with specific required properties:

interface TransactionObj {
[index: string]: number;
pizza: number; // Required
books: number; // Required
job: number; // Required
}

const todaysTransactions: TransactionObj = {


pizza: -10,
books: -5,
job: 50,
dave: 42 // ✅ OK - additional property allowed
};

Readonly Index Signatures

interface TransactionObj {
readonly [index: string]: number;
}

const todaysTransactions: TransactionObj = {


pizza: -10
};

[Link] = 40; // ❌ Error! Index signature is readonly

Limitations

interface TransactionObj {
[index: string]: number;
}

const todaysTransactions: TransactionObj = {


pizza: -10
};

// TypeScript can't prevent accessing non-existent properties


[Link]([Link]); // undefined (no error!)

The keyof Assertion

When you don't have an index signature, use keyof to access properties dynamically:

interface Student {
name: string;
GPA: number;
classes?: number[];
}

const student: Student = {


name: "Doug",
GPA: 3.5,
classes: [100, 200]
};
// Without keyof - ERROR
for (const key in student) {
[Link](student[key]); // ❌ Error!
}

// With keyof assertion - OK


for (const key in student) {
[Link](student[key as keyof Student]); // ✅ OK
}

// Alternative with [Link]


[Link](student).map(key => {
[Link](student[key as keyof typeof student]);
});

keyof with Functions

function logStudentKey(student: Student, key: keyof Student): void {


[Link](`Student ${key}: ${student[key]}`);
}

logStudentKey(student, "name"); // IntelliSense shows: "name" | "GPA" | "classes"


logStudentKey(student, "GPA"); // ✅ OK

Record Utility Type (Alternative to Index Signatures)

// Instead of this:
interface Incomes {
[index: string]: number;
}

// Use Record:
type Streams = "salary" | "bonus" | "sideHustle";
type Incomes = Record<Streams, number>;

const monthlyIncomes: Incomes = {


salary: 500,
bonus: 100,
sideHustle: 250
};

Benefits:

Can use string literal types as keys


More specific than generic string index

Limitations:

Still requires keyof assertion for dynamic access

Can't specify different types for different properties

// Loop still needs assertion


for (const revenue in monthlyIncomes) {
[Link](monthlyIncomes[revenue as keyof Incomes]);
}

Comparison: Index Signature vs Record

Index Signature:

interface Student {
[key: string]: string | number | number[] | undefined;
name: string;
GPA: number;
classes?: number[];
}

Record Type:

type Streams = "salary" | "bonus" | "sideHustle";


type Incomes = Record<Streams, number>;

Chapter 8: Generics

Why Generics?

TypeScript is about strict types, but sometimes we don't know what types will be passed to:

Functions

Interfaces

Type aliases

Classes

Generics provide type variables - placeholders for types that will be specified later.

Basic Generic Function

// Non-generic - only works with strings


function stringEcho(arg: string): string {
return arg;
}
// Generic - works with any type
function echo<T>(arg: T): T {
return arg;
}

Convention: Use T for "Type" (but any name works)

Generic Utility Function Example

const isObj = <T>(arg: T): boolean => {


return (
typeof arg === "object" &&
![Link](arg) &&
arg !== null
);
};

[Link](isObj(true)); // false
[Link](isObj("John")); // false
[Link](isObj([1, 2, 3])); // false
[Link](isObj({ name: "John" })); // true
[Link](isObj(null)); // false

Generics with Complex Logic

const isTrue = <T>(arg: T): { arg: T; is: boolean } => {


if ([Link](arg) && ![Link]) {
return { arg, is: false };
}
if (isObj(arg) && ![Link](arg as keyof T).length) {
return { arg, is: false };
}
return { arg, is: !!arg }; // Double bang converts to boolean
};

[Link](isTrue(false)); // { arg: false, is: false }


[Link](isTrue(0)); // { arg: 0, is: false }
[Link](isTrue(true)); // { arg: true, is: true }
[Link](isTrue(1)); // { arg: 1, is: true }
[Link](isTrue("Dave")); // { arg: "Dave", is: true }
[Link](isTrue("")); // { arg: "", is: false }
[Link](isTrue(null)); // { arg: null, is: false }
[Link](isTrue({})); // { arg: {}, is: false }
[Link](isTrue({ name: "Dave" })); // { arg: {...}, is: true }
[Link](isTrue([])); // { arg: [], is: false }
[Link](isTrue([1, 2])); // { arg: [1, 2], is: true }

Generic with Interface

interface BoolCheck<T> {
value: T;
is: boolean;
}

function checkBoolValue<T>(arg: T): BoolCheck<T> {


if ([Link](arg) && ![Link]) {
return { value: arg, is: false };
}
if (isObj(arg) && ![Link](arg as keyof T).length) {
return { value: arg, is: false };
}
return { value: arg, is: !!arg };
}

Narrowing Generics with extends

interface HasID {
id: number;
}

function processUser<T extends HasID>(user: T): T {


// Process user
return user;
}

[Link](processUser({ id: 1, name: "Dave" })); // ✅ OK


[Link](processUser({ name: "Dave" })); // ❌ Error! No id

Multiple Generic Types

function getUsersProperty<T extends HasID, K extends keyof T>(


users: T[],
key: K
): T[K][] {
return [Link](user => user[key]);
}

const users = [
{
id: 1,
name: "Leanne Graham",
username: "Bret",
email: "Sincere@[Link]"
},
{
id: 2,
name: "Ervin Howell",
username: "Antonette",
email: "Shanna@[Link]"
}
];

[Link](getUsersProperty(users, "email"));
// ["Sincere@[Link]", "Shanna@[Link]"]

[Link](getUsersProperty(users, "username"));
// ["Bret", "Antonette"]

Breakdown:

T extends HasID - T must have an id property

K extends keyof T - K must be a key of T

users: T[] - Array of T objects

key: K - The key to extract

Returns: T[K][] - Array of values at that key

Generic Classes

class StateObject<T> {
private data: T;

constructor(value: T) {
[Link] = value;
}

get state(): T {
return [Link];
}

set state(value: T) {
[Link] = value;
}
}
// TypeScript infers type from initial value
const store = new StateObject("John");
[Link]([Link]); // "John"

[Link] = "Dave"; // ✅ OK
[Link] = 12; // ❌ Error! Must be string

// Explicitly specify type


const myState = new StateObject<(string | number | boolean)[]>([15]);
[Link] = ["Dave", 42, true]; // ✅ OK
[Link]([Link]);

Chapter 9: Utility Types

TypeScript provides many utility types for common type transformations.

Partial

Makes all properties optional:

interface Assignment {
studentId: string;
title: string;
grade: number;
verified?: boolean;
}

function updateAssignment(
assign: Assignment,
propsToUpdate: Partial<Assignment>
): Assignment {
return { ...assign, ...propsToUpdate };
}

const assign1: Assignment = {


studentId: "compsci123",
title: "Final Project",
grade: 0
};

const assignGraded = updateAssignment(assign1, { grade: 95 });

Required

Makes all properties required (opposite of Partial):


function recordAssignment(assign: Required<Assignment>): Assignment {
// Send to database, etc.
return assign;
}

// Must include 'verified' even though it's optional in interface


recordAssignment({ ...assignGraded, verified: true });

Readonly

Makes all properties readonly:

const assignVerified: Readonly<Assignment> = {


...assignGraded,
verified: true
};

[Link] = 88; // ❌ Error! Readonly property

Record<Keys, Type>

Creates an object type with specified keys and value types:

// Simple example
const hexColorMap: Record<string, string> = {
red: "FF0000",
green: "00FF00",
blue: "0000FF"
};

// With string literal types


type Students = "Sara" | "Kelly";
type LetterGrades = "A" | "B" | "C" | "D" | "U";

const finalGrades: Record<Students, LetterGrades> = {


Sara: "B",
Kelly: "U"
};

// With interface values


interface Grades {
assign1: number;
assign2: number;
}
const gradeData: Record<Students, Grades> = {
Sara: { assign1: 85, assign2: 93 },
Kelly: { assign1: 76, assign2: 15 }
};

Pick<Type, Keys>

Creates a type by picking specific properties:

type AssignResult = Pick<Assignment, "studentId" | "grade">;

const score: AssignResult = {


studentId: "k123",
grade: 85
};

Omit<Type, Keys>

Creates a type by omitting specific properties:

type AssignPreview = Omit<Assignment, "grade" | "verified">;

const preview: AssignPreview = {


studentId: "k123",
title: "Final Project"
};

Exclude<UnionType, ExcludedMembers>

Excludes types from a union (works with string literal types, not interfaces):

type LetterGrades = "A" | "B" | "C" | "D" | "U";

type AdjustedGrade = Exclude<LetterGrades, "U">; // "A" | "B" | "C" | "D"

Extract<UnionType, ExtractedMembers>

Extracts types from a union:

type HighGrades = Extract<LetterGrades, "A" | "B">; // "A" | "B"

NonNullable

Removes null and undefined from a type:

type AllPossibleGrades = "Dave" | "John" | null | undefined;

type NamesOnly = NonNullable<AllPossibleGrades>; // "Dave" | "John"


ReturnType

Extracts the return type of a function:

function createNewAssign(title: string, points: number) {


return { title, points };
}

type NewAssign = ReturnType<typeof createNewAssign>;


// { title: string; points: number; }

const tsAssign: NewAssign = createNewAssign("Utility Types", 100);

Benefits:

Type updates automatically when function changes

Useful for library functions you don't control

Parameters

Extracts parameter types as a tuple:

type AssignParams = Parameters<typeof createNewAssign>;


// [title: string, points: number]

const assignArgs: AssignParams = ["Generics", 100];

const tsAssign2: NewAssign = createNewAssign(...assignArgs);

Awaited

Unwraps the type of a Promise:

interface User {
id: number;
name: string;
username: string;
email: string;
}

function fetchUsers(): Promise<User[]> {


return fetch("[Link]
.then(res => [Link]())
.then(data => data)
.catch(error => {
if (error instanceof Error) [Link](error);
});
}

// Without Awaited - gets Promise<User[]>


type FetchUsersReturnType = ReturnType<typeof fetchUsers>;
// Promise<User[]>

// With Awaited - gets User[]


type FetchUsersReturn = Awaited<ReturnType<typeof fetchUsers>>;
// User[]

fetchUsers().then(users => [Link](users));

Quick Reference

Type Aliases vs Interfaces

Type Alias Interface

Can represent ANY type Best for objects/classes

Use for unions, primitives Cannot do unions

Use = syntax No = needed

Can't be merged Can be merged

Visibility Modifiers

Modifier Access

public (default) Everywhere

private Only within class

protected Within class and subclasses

readonly Can't be modified after initialization

Common Patterns

// Generic function
function identity<T>(arg: T): T {
return arg;
}

// Generic with constraint


function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
// Generic class
class Box<T> {
constructor(public contents: T) {}
}

// Generic interface
interface Container<T> {
value: T;
}

Key Takeaways

1. Type Aliases make code more readable and DRY

2. Literal types restrict values to specific options

3. Type assertions tell TypeScript you know better (use carefully!)


4. Classes support full OOP with visibility modifiers

5. Index signatures enable dynamic property access

6. Generics provide type safety with flexibility

7. Utility types offer powerful type transformations

Next: Continue with more advanced TypeScript topics!

TypeScript Tutorial Notes - Part 3

Course: TypeScript for Beginners (Chapters 10-14)


Instructor: Dave Gray

Chapter 10: [Link] + TypeScript

What is Vite?

Pronunciation: "Veet" (not "Vite")

Vite is a fast build tool that helps you start JavaScript, TypeScript, or React projects quickly. It's faster
than Create React App and works with multiple frameworks.

Prerequisites

[Link] - Download LTS version from [Link]

Check version: node -v

Creating a Vite Project


# Create new Vite project
npm create vite@latest

# Follow prompts:
# - Project name: vite-ts-project
# - Framework: vanilla (or react, preact, lit, etc.)
# - Variant: TypeScript

# Navigate to project
cd vite-ts-project

# Install dependencies
npm install
# or
npm i

# Run development server


npm run Dev

# Opens at [Link]

Project Structure

vite-ts-project/
├── src/
│ ├── [Link] # Entry point
│ ├── [Link] # Example component
│ └── [Link] # Environment variable types
├── [Link] # HTML entry
├── [Link] # TypeScript config
├── [Link] # Dependencies & scripts
└── [Link] # Vite configuration

TypeScript Configuration

Vite provides good defaults in [Link] :

Already includes "include": ["src"]

Configured for modern TypeScript usage

Ready to go without modifications

Environment Variables

Location: [Link]
// Access environment variables
[Link]
[Link]
[Link]

// Custom variables
[Link].VITE_API_KEY // Must start with VITE_

Note: Unlike other tools, Vite uses [Link] instead of [Link]

Creating a React + TypeScript Project

npm create vite@latest

# Choose:
# - Project name: vite-react-ts-project
# - Framework: react
# - Variant: TypeScript

cd vite-react-ts-project
npm i
npm run dev

React project includes:

[Link] (instead of .ts)

[Link] component

Vite React plugin pre-configured

TypeScript intellisense ready

[Link] Scripts

{
"scripts": {
"dev": "vite", // Start dev server
"build": "vite build", // Production build
"preview": "vite preview" // Preview production build
}
}

Deployment

Vite has deployment guides for:

Vercel

GitHub Pages
Netlify

Render

Firebase

Check official docs: [Link]/guide/static-deploy

Environment Files

.env # All environments


.[Link] # Local only (ignored by git)
.[Link] # Development only
.[Link] # Production only

Important: .[Link] is automatically ignored by git (already in .gitignore )

Why Vite?

Fast: Much faster than Create React App

Modern: Uses ES modules for development

Flexible: Works with multiple frameworks

TypeScript ready: Built-in TypeScript support

Hot Module Replacement (HMR): Instant updates during development

Chapter 11: TypeScript Project - Simple List App

Project Overview

Build a list application with:

Add items to list

Check items off

Delete individual items

Clear entire list

Persist data in localStorage

Project Setup

npm create vite@latest


# Name: lesson-11
# Framework: vanilla
# Variant: TypeScript

cd lesson-11
npm i
npm run dev
Project Structure

lesson-11/
├── src/
│ ├── css/
│ │ └── [Link]
│ ├── model/
│ │ ├── [Link]
│ │ └── [Link]
│ ├── templates/
│ │ └── [Link]
│ └── [Link]
├── build/
│ └── [Link]
└── [Link]

Model: [Link]

export interface Item {


id: string;
item: string;
checked: boolean;
}

export default class ListItem implements Item {


constructor(
private _id: string = "",
private _item: string = "",
private _checked: boolean = false
) {}

get id(): string {


return this._id;
}
set id(id: string) {
this._id = id;
}

get item(): string {


return this._item;
}
set item(item: string) {
this._item = item;
}
get checked(): boolean {
return this._checked;
}
set checked(checked: boolean) {
this._checked = checked;
}
}

Key concepts:

Private properties with underscore prefix

Getters and setters without underscore

Visibility modifiers in constructor params (shorthand)

Model: [Link] (Singleton)

import ListItem from "./ListItem";

interface List {
list: ListItem[];
load(): void;
save(): void;
clearList(): void;
addItem(itemObj: ListItem): void;
removeItem(id: string): void;
}

export default class FullList implements List {


static instance: FullList = new FullList();

private constructor(private _list: ListItem[] = []) {}

get list(): ListItem[] {


return this._list;
}

load(): void {
const storedList: string | null = [Link]("myList");
if (typeof storedList !== "string") return;

const parsedList: { _id: string; _item: string; _checked: boolean }[] =


[Link](storedList);
[Link]((itemObj) => {
const newListItem = new ListItem(
itemObj._id,
itemObj._item,
itemObj._checked
);
[Link](newListItem);
});
}

save(): void {
[Link]("myList", [Link](this._list));
}

clearList(): void {
this._list = [];
[Link]();
}

addItem(itemObj: ListItem): void {


this._list.push(itemObj);
[Link]();
}

removeItem(id: string): void {


this._list = this._list.filter((item) => [Link] !== id);
[Link]();
}
}

Singleton pattern:

Private constructor

Static instance property

Only one instance throughout app

Template: [Link]

import FullList from "../model/FullList";

interface DOMList {
ul: HTMLUListElement;
clear(): void;
render(fullList: FullList): void;
}

export default class ListTemplate implements DOMList {


static instance: ListTemplate = new ListTemplate();

ul: HTMLUListElement;

private constructor() {
[Link] = [Link]("listItems") as HTMLUListElement;
}

clear(): void {
[Link] = "";
}

render(fullList: FullList): void {


[Link]();

[Link]((item) => {
const li = [Link]("li") as HTMLLIElement;
[Link] = "item";

const check = [Link]("input") as HTMLInputElement;


[Link] = "checkbox";
[Link] = [Link];
[Link] = [Link];
[Link]("change", () => {
[Link] = ![Link];
[Link]();
});
[Link](check);

const label = [Link]("label") as HTMLLabelElement;


[Link] = [Link];
[Link] = [Link];
[Link](label);

const button = [Link]("button") as HTMLButtonElement;


[Link] = "button";
[Link] = "X";
[Link]("click", () => {
[Link]([Link]);
[Link](fullList);
});
[Link](button);

[Link](li);
});
}
}

Main Application: [Link]

import "./css/[Link]";
import FullList from "./model/FullList";
import ListItem from "./model/ListItem";
import ListTemplate from "./templates/ListTemplate";

const initApp = (): void => {


const fullList = [Link];
const template = [Link];

// Form submit handler


const itemEntryForm = [Link](
"itemEntryForm"
) as HTMLFormElement;

[Link]("submit", (event: SubmitEvent): void => {


[Link]();

const input = [Link]("newItem") as HTMLInputElement;


const newEntryText: string = [Link]();
if (![Link]) return;

const itemId: number = [Link]


? parseInt([Link][[Link] - 1].id) + 1
: 1;

const newItem = new ListItem([Link](), newEntryText);


[Link](newItem);
[Link](fullList);
[Link] = "";
});

// Clear button handler


const clearItems = [Link](
"clearItemsButton"
) as HTMLButtonElement;

[Link]("click", (): void => {


[Link]();
[Link]();
});

// Load and render on startup


[Link]();
[Link](fullList);
};

[Link]("DOMContentLoaded", initApp);

Key Patterns

Type Assertions:

const element = [Link]("id") as HTMLInputElement;

Singleton Access:

const instance = [Link];

LocalStorage:

// Save
[Link]("key", [Link](data));

// Load
const data = [Link]("key");
const parsed = [Link](data);

Chapter 12: React + TypeScript Basics

Project Setup

npm create vite@latest


# Name: lesson-12
# Framework: react
# Variant: TypeScript

cd lesson-12
npm i
npm run dev
File Extensions

.tsx - TypeScript + JSX (for components)

.ts - TypeScript only

Basic Function Component

import { ReactElement } from "react";

type HeadingProps = {
title: string;
};

const Heading = ({ title }: HeadingProps): ReactElement => {


return <h1>{title}</h1>;
};

export default Heading;

Return types:

ReactElement - Specific element

[Link] - Inferred (most common)

Components with Children

Modern approach (React 18+):

import { ReactNode } from "react";

type SectionProps = {
title?: string;
children: ReactNode; // Must be explicit in React 18+
};

const Section = ({
children,
title = "My Subheading"
}: SectionProps) => {
return (
<section>
<h2>{title}</h2>
{children}
</section>
);
};
export default Section;

What changed in React 18:

Children must be explicitly typed

Cannot implicitly include children anymore

ReactNode is the recommended type for children

Deprecated patterns:

// ❌ Don't use (deprecated)


const Component: [Link]<Props> = ({ children }) => { };

// ❌ Don't use (being deprecated)


[Link] = { title: "Default" };

// ✅ Use default parameters instead


const Component = ({ title = "Default" }: Props) => { };

useState Hook

import { useState } from "react";

const Counter = () => {


// Type inference works
const [count, setCount] = useState(0); // inferred as number

// Explicit typing
const [count, setCount] = useState<number>(0);

// Union types
const [data, setData] = useState<number | null>(null);

// With interfaces
interface User {
id: number;
name: string;
}
const [user, setUser] = useState<User | null>(null);
const [users, setUsers] = useState<User[]>([]);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
</div>
);
};

Passing State and Functions

type CounterProps = {
setCount: [Link]<[Link]<number>>;
children: ReactNode;
};

const Counter = ({ setCount, children }: CounterProps) => {


return (
<>
<h1>{children}</h1>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
</>
);
};

// Usage in parent
const App = () => {
const [count, setCount] = useState(1);

return (
<Counter setCount={setCount}>
Count is: {count}
</Counter>
);
};

Generic List Component

import { ReactNode } from "react";

interface ListProps<T> {
items: T[];
render: (item: T) => ReactNode;
}

// Add comma after T to help TypeScript recognize generic


const List = <T,>({ items, render }: ListProps<T>) => {
return (
<ul>
{[Link]((item, i) => (
<li key={i}>{render(item)}</li>
))}
</ul>
);
};

// Usage
const App = () => {
const items = ["coffee", "tacos", "code"];

return (
<List
items={items}
render={(item: string) => <span className="bold">{item}</span>}
/>
);
};

Generic syntax note:

// Need comma after T with arrow functions


const Component = <T,>({ props }: Props<T>) => { };

// Or use extends
const Component = <T extends {}>({ props }: Props<T>) => { };

Chapter 13: React Hooks + TypeScript

useState Hook (Review)

import { useState } from "react";

// Inference (recommended for primitives)


const [count, setCount] = useState(0); // number
const [name, setName] = useState(""); // string
const [isActive, setActive] = useState(false); // boolean

// Explicit typing (for complex types)


interface User {
id: number;
username: string;
}

const [users, setUsers] = useState<User[]>([]);


const [user, setUser] = useState<User | null>(null);

// Type assertion (not recommended)


const [user, setUser] = useState({} as User); // Lying to compiler!

useEffect Hook

import { useEffect } from "react";

useEffect(() => {
[Link]("mounting");

// Cleanup function
return () => {
[Link]("unmounting");
};
}, []); // Empty array - runs once on mount

// With dependencies
useEffect(() => {
[Link]("users:", users);
}, [users]); // Runs when users changes

Note: No TypeScript-specific typing needed - useEffect doesn't return values.

useCallback Hook

import { useCallback, MouseEvent, KeyboardEvent } from "react";

const App = () => {


const [count, setCount] = useState(0);

// Basic useCallback
const addTwo = useCallback((): void => {
setCount(prev => prev + 2);
}, []); // No dependencies

// With event typing


const handleClick = useCallback(
(e: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>): void =>
{
setCount(prev => prev + 1);
},
[]
);

return <button onClick={addTwo}>Add 2</button>;


};

Event types:

MouseEvent<HTMLButtonElement>

KeyboardEvent<HTMLButtonElement>

ChangeEvent<HTMLInputElement>

FormEvent<HTMLFormElement>

FocusEvent<HTMLInputElement>

useMemo Hook

import { useMemo } from "react";

type FibFunc = (n: number) => number;

const fib: FibFunc = (n) => {


if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
};

const App = () => {


const myNum = 37;

// Memoized value
const result = useMemo<number>(() => fib(myNum), [myNum]);

return <h2>{result}</h2>;
};

When to use:

Expensive calculations

Complex object creation

Avoid re-computation on every render

useRef Hook

import { useRef } from "react";


const App = () => {
// Type the element explicitly
const inputRef = useRef<HTMLInputElement>(null);

// Non-null assertion alternative


const inputRef = useRef<HTMLInputElement>(null!);

// Access with optional chaining (safer)


[Link]([Link]); // Element or null
[Link]([Link]?.value); // Value or undefined

return <input type="text" ref={inputRef} />;


};

Important:

Changing [Link] doesn't trigger re-render

Use optional chaining when accessing values

Specify element type: HTMLInputElement , HTMLDivElement , etc.

Chapter 14: React useReducer + TypeScript

Basic Counter with useState (Before)

import { useState } from "react";

const Counter = () => {


const [count, setCount] = useState<number>(1);

const increment = () => setCount(prev => prev + 1);


const decrement = () => setCount(prev => prev - 1);

return (
<>
<h1>Count is: {count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
);
};

Refactored with useReducer

1. Define Initial State


const initState = { count: 0 };

TypeScript infers: { count: number }

2. Define Action Types

// Using enum (controversial but valid)


const enum ReducerActionType {
INCREMENT,
DECREMENT,
NEW_INPUT
}

// Alternative: String literal union


type ReducerActionType = "INCREMENT" | "DECREMENT" | "NEW_INPUT";

3. Define Action Type

type ReducerAction = {
type: ReducerActionType;
payload?: string; // Optional for actions that don't need it
};

4. Create Reducer Function

const reducer = (
state: typeof initState,
action: ReducerAction
): typeof initState => {
switch ([Link]) {
case [Link]:
return { ...state, count: [Link] + 1 };

case [Link]:
return { ...state, count: [Link] - 1 };

case ReducerActionType.NEW_INPUT:
return { ...state, text: [Link] ?? "" };

default:
throw new Error();
}
};

Using typeof for state type:


Infers type from initial state

Keeps types in sync automatically

5. Use in Component

import { useReducer, ChangeEvent } from "react";

const Counter = () => {


const [state, dispatch] = useReducer(reducer, initState);

const increment = () =>


dispatch({ type: [Link] });

const decrement = () =>


dispatch({ type: [Link] });

const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {


dispatch({
type: ReducerActionType.NEW_INPUT,
payload: [Link]
});
};

return (
<>
<h1>Count is: {[Link]}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<input type="text" onChange={handleTextInput} />
<h2>{[Link]}</h2>
</>
);
};

Complete Example with Text Input

import { useReducer, ChangeEvent } from "react";

// Initial state
const initState = {
count: 0,
text: ""
};

// Action types
const enum ReducerActionType {
INCREMENT,
DECREMENT,
NEW_INPUT
}

// Action type definition


type ReducerAction = {
type: ReducerActionType;
payload?: string;
};

// Reducer function
const reducer = (
state: typeof initState,
action: ReducerAction
): typeof initState => {
switch ([Link]) {
case [Link]:
return { ...state, count: [Link] + 1 };

case [Link]:
return { ...state, count: [Link] - 1 };

case ReducerActionType.NEW_INPUT:
return { ...state, text: [Link] ?? "" };

default:
throw new Error();
}
};

// Component
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initState);

const increment = () =>


dispatch({ type: [Link] });

const decrement = () =>


dispatch({ type: [Link] });

const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {


dispatch({
type: ReducerActionType.NEW_INPUT,
payload: [Link]
});
};

return (
<>
<h1>Count is: {[Link]}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<input type="text" onChange={handleTextInput} />
<h2>{[Link]}</h2>
</>
);
};

export default Counter;

Handling Optional Payloads

Problem:

text: [Link] // Error: Type 'string | undefined' not assignable

Solution - Nullish Coalescing:

text: [Link] ?? "" // Use empty string if undefined

When to Use useReducer

Use useReducer when:

Complex state logic with multiple sub-values

State updates depend on previous state

Need to share state logic between components (with Context)

Many related state updates

Stick with useState when:

Simple state (single value)

Independent state updates

No complex state transitions

Quick Reference
React Component Patterns

// Basic component
type Props = { title: string };
const Component = ({ title }: Props) => <h1>{title}</h1>;

// With children
type Props = { children: ReactNode };
const Component = ({ children }: Props) => <div>{children}</div>;

// With optional props


type Props = { title?: string };
const Component = ({ title = "Default" }: Props) => <h1>{title}</h1>;

Common Event Types

MouseEvent<HTMLButtonElement>
ChangeEvent<HTMLInputElement>
FormEvent<HTMLFormElement>
KeyboardEvent<HTMLInputElement>
FocusEvent<HTMLInputElement>

Hook Patterns

// useState
const [state, setState] = useState<Type>(initialValue);

// useReducer
const [state, dispatch] = useReducer(reducer, initialState);

// useRef
const ref = useRef<HTMLInputElement>(null);

// useCallback
const fn = useCallback(() => { }, [deps]);

// useMemo
const value = useMemo(() => compute(), [deps]);

Key Takeaways

1. Vite is faster and simpler than Create React App

2. React 18 requires explicit ReactNode type for children

3. Deprecated: [Link] , defaultProps


4. useState often infers types automatically

5. useReducer is great for complex state with multiple sub-values

6. Event types must be specified in React 18+

7. Generic components need comma or extends for TypeScript recognition

8. useRef requires element type specification

9. Singleton pattern useful for global state management

Next: useContext and custom hooks with TypeScript!

TypeScript Tutorial Notes - Part 4 (Complete Final)

Course: TypeScript for Beginners (Chapters 15-17 Complete)


Instructor: Dave Gray

Chapter 15: React useContext + TypeScript (Complete)

Starting Point

Begin with code from Lesson 14 (useReducer). We'll extract all state and logic from the Counter
component into a Context.

Why Use Context?

Problems it solves:

Unclutters components

Organizes state and logic by feature

Avoids prop drilling

Easier to maintain as app grows

Complete Context Implementation


1. Define State and Action Types

// State type
type StateType = {
count: number;
text: string;
};

const initState: StateType = { count: 0, text: "" };

// Action types
const enum ReducerActionType {
INCREMENT,
DECREMENT,
NEW_INPUT
}

type ReducerAction = {
type: ReducerActionType;
payload?: string;
};

2. Create Reducer Function

const reducer = (
state: StateType,
action: ReducerAction
): StateType => {
switch ([Link]) {
case [Link]:
return { ...state, count: [Link] + 1 };

case [Link]:
return { ...state, count: [Link] - 1 };

case ReducerActionType.NEW_INPUT:
return { ...state, text: [Link] ?? "" };

default:
throw new Error();
}
};

3. Create Internal Custom Hook (NOT Exported)

import { useReducer, useCallback, ChangeEvent } from "react";

const useCounterContext = (initState: StateType) => {


const [state, dispatch] = useReducer(reducer, initState);

// Wrap in useCallback for referential equality


const increment = useCallback(
() => dispatch({ type: [Link] }),
[]
);

const decrement = useCallback(


() => dispatch({ type: [Link] }),
[]
);

const handleTextInput = useCallback(


(e: ChangeEvent<HTMLInputElement>) => {
dispatch({
type: ReducerActionType.NEW_INPUT,
payload: [Link]
});
},
[]
);

return { state, increment, decrement, handleTextInput };


};

Critical: Wrap functions in useCallback to prevent unnecessary re-renders!

4. Create Context Type

// Use ReturnType utility to extract type from hook


type UseCounterContextType = ReturnType<typeof useCounterContext>;

// Initial context state (dummy functions for initialization)


const initContextState: UseCounterContextType = {
state: initState,
increment: () => {},
decrement: () => {},
handleTextInput: (e: ChangeEvent<HTMLInputElement>) => {}
};

Why dummy functions? Context needs initial values, but they'll be replaced by provider.

5. Create Context

import { createContext, ReactElement } from "react";

export const CounterContext = createContext<UseCounterContextType>(


initContextState
);

6. Create Children Type (React 18+)

type ChildrenType = {
children?: ReactElement | ReactElement[];
};

Important: Children must be explicitly typed in React 18+

7. Create Provider

export const CounterProvider = ({


children,
...initState
}: ChildrenType & StateType): ReactElement => {
return (
<[Link] value={useCounterContext(initState)}>
{children}
</[Link]>
);
};

export default CounterContext;

8. Wrap App with Provider

// [Link]
import { CounterProvider } from './context/CounterContext';
import { initState } from './context/CounterContext';

<CounterProvider count={[Link]} text={[Link]}>


<Counter />
</CounterProvider>

9. Create Custom Hook for Consumers

// hooks/[Link]
import { useContext } from "react";
import CounterContext from "../context/CounterContext";
import { UseCounterContextType } from "../context/CounterContext";

const useCounter = (): UseCounterContextType => {


return useContext(CounterContext);
};

export default useCounter;

Why create this hook? Cleaner than importing both useContext and CounterContext .

10. Use in Components


import useCounter from "../hooks/useCounter";

const Counter = () => {


const { state, increment, decrement } = useCounter();

return (
<>
<h1>Count: {[Link]}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
);
};

Complete File Structure

src/
├── context/
│ └── [Link]
├── hooks/
│ └── [Link]
└── components/
└── [Link]

What to Export from Context

// [Link] exports:
export const CounterContext; // Context itself
export const CounterProvider; // Provider component
export const initState; // Initial state
export type UseCounterContextType; // Type (if needed elsewhere)

// DON'T export:
// - useCounterContext (internal hook)

Key Patterns Summary

1. Internal hook - Creates state/logic, NOT exported

2. ReturnType utility - Gets type from hook automatically

3. useCallback - Prevents function recreation

4. Custom consumer hook - Cleaner component imports

5. Children typing - Required in React 18+

Chapters 16-17: Shopping Cart Project (Complete)


Project Overview

Features:

Display products from data

Add products to cart

Update quantities in cart

Remove items from cart

Display totals (items & price)

Toggle product/cart views

Format currency properly

Memoize components for performance

Project Setup

npm create vite@latest


# Name: lesson-16
# Framework: react
# Variant: TypeScript

cd lesson-16
npm i
npm run dev

Complete File Structure

src/
├── components/
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── context/
│ ├── [Link]
│ └── [Link]
├── hooks/
│ ├── [Link]
│ └── [Link]
├── images/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── [Link]
├── [Link]
└── [Link]

data/
└── [Link]

Part 1: Context Setup

Products Data

// data/[Link]
{
"products": [
{
"sku": "item0001",
"name": "Widget",
"price": 9.99
},
{
"sku": "item0002",
"name": "Premium Widget",
"price": 19.99
},
{
"sku": "item0003",
"name": "Deluxe Widget",
"price": 29.99
}
]
}

[Link] (Complete)

import { createContext, ReactElement, useState } from "react";

// Product type
export type ProductType = {
sku: string;
name: string;
price: number;
};

// Initial state (hardcoded for deployment)


const initState: ProductType[] = [
{ "sku": "item0001", "name": "Widget", "price": 9.99 },
{ "sku": "item0002", "name": "Premium Widget", "price": 19.99 },
{ "sku": "item0003", "name": "Deluxe Widget", "price": 29.99 }
];

// Alternative: Fetch from JSON server (dev only)


/*
import { useEffect } from "react";

const initState: ProductType[] = [];

// In provider component:
useEffect(() => {
const fetchProducts = async (): Promise<ProductType[]> => {
const data = await fetch("[Link]
.then(res => [Link]())
.catch(err => {
if (err instanceof Error) [Link]([Link]);
});
return data;
};

fetchProducts().then(products => setProducts(products));


}, []);
*/

// Context type
export type UseProductsContextType = {
products: ProductType[];
};

// Initial context state


const initContextState: UseProductsContextType = {
products: []
};

// Create context
const ProductsContext = createContext<UseProductsContextType>(
initContextState
);

// Children type
type ChildrenType = {
children?: ReactElement | ReactElement[];
};

// Provider
export const ProductsProvider = ({
children
}: ChildrenType): ReactElement => {
const [products] = useState<ProductType[]>(initState);

return (
<[Link] value={{ products }}>
{children}
</[Link]>
);
};

export default ProductsContext;

Deployment note: Use hardcoded initState for production. JSON server only for development.

[Link] (Complete)

import { useReducer, useMemo, createContext, ReactElement } from "react";

// Types
export type CartItemType = {
sku: string;
name: string;
price: number;
qty: number;
};

type CartStateType = {
cart: CartItemType[];
};

const initCartState: CartStateType = { cart: [] };

// Action types (using object instead of enum)


const REDUCER_ACTION_TYPE = {
ADD: "ADD",
REMOVE: "REMOVE",
QUANTITY: "QUANTITY",
SUBMIT: "SUBMIT"
};

export type ReducerActionType = typeof REDUCER_ACTION_TYPE;

export type ReducerAction = {


type: string;
payload?: CartItemType;
};

// Reducer function
const reducer = (
state: CartStateType,
action: ReducerAction
): CartStateType => {
switch ([Link]) {
case REDUCER_ACTION_TYPE.ADD: {
if (![Link]) {
throw new Error("[Link] missing in ADD action");
}

const { sku, name, price } = [Link];

const filteredCart: CartItemType[] = [Link](


item => [Link] !== sku
);

const itemExists: CartItemType | undefined = [Link](


item => [Link] === sku
);

const qty: number = itemExists ? [Link] + 1 : 1;

return {
...state,
cart: [...filteredCart, { sku, name, price, qty }]
};
}

case REDUCER_ACTION_TYPE.REMOVE: {
if (![Link]) {
throw new Error("[Link] missing in REMOVE action");
}
const { sku } = [Link];

const filteredCart: CartItemType[] = [Link](


item => [Link] !== sku
);

return { ...state, cart: [...filteredCart] };


}

case REDUCER_ACTION_TYPE.QUANTITY: {
if (![Link]) {
throw new Error("[Link] missing in QUANTITY action");
}

const { sku, qty } = [Link];

const itemExists: CartItemType | undefined = [Link](


item => [Link] === sku
);

if (!itemExists) {
throw new Error("Item must exist to update quantity");
}

const updatedItem: CartItemType = { ...itemExists, qty };

const filteredCart: CartItemType[] = [Link](


item => [Link] !== sku
);

return {
...state,
cart: [...filteredCart, updatedItem]
};
}

case REDUCER_ACTION_TYPE.SUBMIT: {
return { ...state, cart: [] };
}

default:
throw new Error("Unidentified reducer action type");
}
};

// Internal hook (NOT exported)


const useCartContext = (initState: CartStateType) => {
const [state, dispatch] = useReducer(reducer, initState);

const REDUCER_ACTIONS = useMemo(() => REDUCER_ACTION_TYPE, []);

const totalItems: number = [Link]((prev, item) => {


return prev + [Link];
}, 0);

const totalPrice: string = new [Link]("en-US", {


style: "currency",
currency: "USD"
}).format(
[Link]((prev, item) => {
return prev + [Link] * [Link];
}, 0)
);

const cart = [Link]((a, b) => {


const itemA = Number([Link](-4));
const itemB = Number([Link](-4));
return itemA - itemB;
});

return { dispatch, REDUCER_ACTIONS, totalItems, totalPrice, cart };


};

// Context type
export type UseCartContextType = ReturnType<typeof useCartContext>;

// Initial context state


const initCartContextState: UseCartContextType = {
dispatch: () => {},
REDUCER_ACTIONS: REDUCER_ACTION_TYPE,
totalItems: 0,
totalPrice: "",
cart: []
};
// Create context
const CartContext = createContext<UseCartContextType>(initCartContextState);

// Children type
type ChildrenType = {
children?: ReactElement | ReactElement[];
};

// Provider
export const CartProvider = ({ children }: ChildrenType): ReactElement => {
return (
<[Link] value={useCartContext(initCartState)}>
{children}
</[Link]>
);
};

export default CartContext;

Key calculations:

totalItems - Sum all quantities

totalPrice - Formatted currency string

cart - Sorted by SKU number

REDUCER_ACTIONS - Memoized for performance

Part 2: Custom Hooks

[Link]

import { useContext } from "react";


import CartContext from "../context/CartProvider";
import { UseCartContextType } from "../context/CartProvider";

const useCart = (): UseCartContextType => {


return useContext(CartContext);
};

export default useCart;

[Link]

import { useContext } from "react";


import ProductsContext from "../context/ProductsProvider";
import { UseProductsContextType } from "../context/ProductsProvider";

const useProducts = (): UseProductsContextType => {


return useContext(ProductsContext);
};

export default useProducts;

Why create these? Much cleaner than importing both useContext and the Context in every
component.

Part 3: App Structure

[Link] - Wrap with Providers

import { StrictMode } from "react";


import { createRoot } from "react-dom/client";
import App from "./[Link]";
import "./[Link]";

import { CartProvider } from "./context/CartProvider";


import { ProductsProvider } from "./context/ProductsProvider";

createRoot([Link]("root")!).render(
<StrictMode>
<ProductsProvider>
<CartProvider>
<App />
</CartProvider>
</ProductsProvider>
</StrictMode>
);

Order matters: Products wraps Cart (Cart depends on Products).

[Link] (Complete)

import { useState } from "react";


import Header from "./components/Header";
import Footer from "./components/Footer";
import Cart from "./components/Cart";
import ProductList from "./components/ProductList";

function App() {
const [viewCart, setViewCart] = useState<boolean>(false);
const pageContent = viewCart ? <Cart /> : <ProductList />;

const content = (
<>
<Header viewCart={viewCart} setViewCart={setViewCart} />
{pageContent}
<Footer viewCart={viewCart} />
</>
);

return content;
}

export default App;

Part 4: Header & Navigation

[Link]

import Nav from "./Nav";


import useCart from "../hooks/useCart";

type PropsType = {
viewCart: boolean;
setViewCart: [Link]<[Link]<boolean>>;
};

const Header = ({ viewCart, setViewCart }: PropsType) => {


const { totalItems, totalPrice } = useCart();

const content = (
<header className="header">
<div className="header__title-bar">
<h1>Acme Co.</h1>
<div className="header__price-box">
<p>Total Items: {totalItems}</p>
<p>Total Price: {totalPrice}</p>
</div>
</div>
<Nav viewCart={viewCart} setViewCart={setViewCart} />
</header>
);
return content;
};

export default Header;

[Link]

type PropsType = {
viewCart: boolean;
setViewCart: [Link]<[Link]<boolean>>;
};

const Nav = ({ viewCart, setViewCart }: PropsType) => {


const button = viewCart
? <button onClick={() => setViewCart(false)}>View Products</button>
: <button onClick={() => setViewCart(true)}>View Cart</button>;

const content = <nav className="nav">{button}</nav>;

return content;
};

export default Nav;

[Link]

import useCart from "../hooks/useCart";

type PropsType = {
viewCart: boolean;
};

const Footer = ({ viewCart }: PropsType) => {


const { totalItems, totalPrice } = useCart();

const year: number = new Date().getFullYear();

const pageContent = viewCart ? (


<p>Shopping Cart &copy; {year}</p>
) : (
<>
<p>Total Items: {totalItems}</p>
<p>Total Price: {totalPrice}</p>
<p>Shopping Cart &copy; {year}</p>
</>
);

const content = <footer className="footer">{pageContent}</footer>;

return content;
};

export default Footer;

Part 5: Product Display

Critical: Dynamic Images with Vite

Problem: require() doesn't work with Vite

Old way (doesn't work with Vite):

// ❌ DON'T DO THIS
const img: string = require(`../images/${[Link]}.jpg`);

New way (works with Vite AND Create React App):

// ✅ DO THIS
const img: string = new URL(
`../images/${[Link]}.jpg`,
[Link]
).href;

How it works:

Creates URL object from relative path

[Link] provides base URL

.href extracts the string path

Vite recognizes this pattern and updates URLs in build

Development vs Production:

Dev: Points to local file path

Production: Points to bundled asset path

Vite automatically handles the conversion!

[Link]

import { ReactElement } from "react";


import useCart from "../hooks/useCart";
import useProducts from "../hooks/useProducts";
import Product from "./Product";

const ProductList = () => {


const { dispatch, REDUCER_ACTIONS, cart } = useCart();
const { products } = useProducts();

let pageContent: ReactElement | ReactElement[] = <p>Loading...</p>;

if (products?.length) {
pageContent = [Link](product => {
const inCart: boolean = [Link](item => [Link] === [Link]);

return (
<Product
key={[Link]}
product={product}
dispatch={dispatch}
REDUCER_ACTIONS={REDUCER_ACTIONS}
inCart={inCart}
/>
);
});
}

const content = (
<main className="main main--products">
{pageContent}
</main>
);

return content;
};

export default ProductList;

[Link] (Without Memoization)

import { ReactElement } from "react";


import { ProductType } from "../context/ProductsProvider";
import { ReducerActionType, ReducerAction } from "../context/CartProvider";

type PropsType = {
product: ProductType;
dispatch: [Link]<ReducerAction>;
REDUCER_ACTIONS: ReducerActionType;
inCart: boolean;
};

const Product = ({
product,
dispatch,
REDUCER_ACTIONS,
inCart
}: PropsType): ReactElement => {
const img: string = new URL(
`../images/${[Link]}.jpg`,
[Link]
).href;

const onAddToCart = () =>


dispatch({
type: REDUCER_ACTIONS.ADD,
payload: { ...product, qty: 1 }
});

const itemInCart = inCart ? " → ✔️" : null;

const content = (
<article className="product">
<h3>{[Link]}</h3>
<img src={img} alt={[Link]} className="product__img" />
<p>
{new [Link]("en-US", {
style: "currency",
currency: "USD"
}).format([Link])}
{itemInCart}
</p>
<button onClick={onAddToCart}>Add to Cart</button>
</article>
);

return content;
};

export default Product;


Part 6: Cart Display

[Link]

import { useState } from "react";


import useCart from "../hooks/useCart";
import CartLineItem from "./CartLineItem";

const Cart = () => {


const [confirm, setConfirm] = useState<boolean>(false);
const { dispatch, REDUCER_ACTIONS, totalItems, totalPrice, cart } = useCart();

const onSubmitOrder = () => {


dispatch({ type: REDUCER_ACTIONS.SUBMIT });
setConfirm(true);
};

const pageContent = confirm ? (


<h2>Thank you for your order.</h2>
) : (
<>
<h2 className="offscreen">Cart</h2>
<ul className="cart">
{[Link](item => (
<CartLineItem
key={[Link]}
item={item}
dispatch={dispatch}
REDUCER_ACTIONS={REDUCER_ACTIONS}
/>
))}
</ul>
<div className="cart__totals">
<p>Total Items: {totalItems}</p>
<p>Total Price: {totalPrice}</p>
<button
className="cart__submit"
disabled={!totalItems}
onClick={onSubmitOrder}
>
Place Order
</button>
</div>
</>
);

const content = <main className="main main--cart">{pageContent}</main>;

return content;
};

export default Cart;

[Link] (Without Memoization)

import { ChangeEvent, ReactElement } from "react";


import { CartItemType } from "../context/CartProvider";
import { ReducerAction, ReducerActionType } from "../context/CartProvider";

type PropsType = {
item: CartItemType;
dispatch: [Link]<ReducerAction>;
REDUCER_ACTIONS: ReducerActionType;
};

const CartLineItem = ({
item,
dispatch,
REDUCER_ACTIONS
}: PropsType): ReactElement => {
const img: string = new URL(
`../images/${[Link]}.jpg`,
[Link]
).href;

const lineTotal: number = [Link] * [Link];

const highestQty: number = 20 > [Link] ? 20 : [Link];

const optionValues: number[] = [...Array(highestQty).keys()].map(i => i + 1);

const options: ReactElement[] = [Link](val => (


<option key={`opt${val}`} value={val}>
{val}
</option>
));

const onChangeQty = (e: ChangeEvent<HTMLSelectElement>) => {


dispatch({
type: REDUCER_ACTIONS.QUANTITY,
payload: { ...item, qty: Number([Link]) }
});
};

const onRemoveFromCart = () =>


dispatch({ type: REDUCER_ACTIONS.REMOVE, payload: item });

const content = (
<li className="cart__item">
<img src={img} alt={[Link]} className="cart__img" />
<div aria-label="Item Name">{[Link]}</div>
<div aria-label="Price Per Item">
{new [Link]("en-US", {
style: "currency",
currency: "USD"
}).format([Link])}
</div>

<label htmlFor="itemQty" className="offscreen">


Item Quantity
</label>
<select
name="itemQty"
id="itemQty"
className="cart__select"
value={[Link]}
aria-label="Item Quantity"
onChange={onChangeQty}
>
{options}
</select>

<div className="cart__item-subtotal" aria-label="Line Item Subtotal">


{new [Link]("en-US", {
style: "currency",
currency: "USD"
}).format(lineTotal)}
</div>

<button
className="cart__button"
aria-label="Remove Item From Cart"
title="Remove Item From Cart"
onClick={onRemoveFromCart}
>

</button>
</li>
);

return content;
};

export default CartLineItem;

Quantity dropdown logic:

Default max: 20

If [Link] > 20, max becomes [Link]

Creates array: [1, 2, 3, ..., max]

Part 7: Performance Optimization with [Link]

Why Memoize?

Problem: Objects lack referential equality

Product/CartItem objects recreated on every render

Causes unnecessary re-renders of child components

Solution: Use [Link] with custom comparison function

Memoized Product Component

import { ReactElement, memo } from "react";


import { ProductType } from "../context/ProductsProvider";
import { ReducerActionType, ReducerAction } from "../context/CartProvider";

type PropsType = {
product: ProductType;
dispatch: [Link]<ReducerAction>;
REDUCER_ACTIONS: ReducerActionType;
inCart: boolean;
};

const Product = ({
product,
dispatch,
REDUCER_ACTIONS,
inCart
}: PropsType): ReactElement => {
// ... component code (same as before)
};

// Comparison function
function areProductsEqual(
{ product: prevProduct, inCart: prevInCart }: PropsType,
{ product: nextProduct, inCart: nextInCart }: PropsType
) {
return (
[Link](prevProduct).every(key => {
return (
prevProduct[key as keyof ProductType] ===
nextProduct[key as keyof ProductType]
);
}) && prevInCart === nextInCart
);
}

// Memoized component
const MemoizedProduct = memo<PropsType>(Product, areProductsEqual);

export default MemoizedProduct;

How comparison works:

1. [Link](prevProduct) - Get array of keys

2. .every() - Check all keys match

3. key as keyof ProductType - TypeScript assertion for dynamic access

4. Compare each property value

5. Also compare inCart boolean separately

Memoized CartLineItem Component

import { ChangeEvent, ReactElement, memo } from "react";


import { CartItemType } from "../context/CartProvider";
import { ReducerAction, ReducerActionType } from "../context/CartProvider";

type PropsType = {
item: CartItemType;
dispatch: [Link]<ReducerAction>;
REDUCER_ACTIONS: ReducerActionType;
};

const CartLineItem = ({
item,
dispatch,
REDUCER_ACTIONS
}: PropsType): ReactElement => {
// ... component code (same as before)
};

// Comparison function
function areItemsEqual(
{ item: prevItem }: PropsType,
{ item: nextItem }: PropsType
) {
return [Link](prevItem).every(key => {
return (
prevItem[key as keyof CartItemType] ===
nextItem[key as keyof CartItemType]
);
});
}

// Memoized component
const MemoizedCartLineItem = memo<PropsType>(CartLineItem, areItemsEqual);

export default MemoizedCartLineItem;

What gets memoized:

item object properties

NOT dispatch (already has referential equality)

NOT REDUCER_ACTIONS (memoized in context with useMemo)

Testing Memoization

Use React DevTools:

1. Enable "Highlight updates when components render"

2. Add item to cart - only that product should highlight

3. Change quantity - only that cart line item should highlight

4. Other components should NOT re-render


Complete Project Patterns

Currency Formatting

new [Link]("en-US", {
style: "currency",
currency: "USD"
}).format(price)

Returns: "$9.99" (formatted string with symbol)

Reducer Pattern for ADD

case REDUCER_ACTION_TYPE.ADD: {
const { sku, name, price } = [Link];

// Filter out item if exists


const filteredCart = [Link](item => [Link] !== sku);

// Check if item exists


const itemExists = [Link](item => [Link] === sku);

// Calculate new quantity


const qty = itemExists ? [Link] + 1 : 1;

// Return new state


return {
...state,
cart: [...filteredCart, { sku, name, price, qty }]
};
}

Logic:

1. Remove item from cart (if exists)


2. Check if item existed

3. Increment qty or set to 1


4. Add item back with new qty

Dynamic Quantity Options

// Max 20, or item qty if higher


const highestQty: number = 20 > [Link] ? 20 : [Link];

// Create array [1, 2, 3, ..., highestQty]


const optionValues: number[] = [...Array(highestQty).keys()].map(i => i + 1);

// Map to option elements


const options: ReactElement[] = [Link](val => (
<option key={`opt${val}`} value={val}>{val}</option>
));

Sort Cart by SKU

const cart = [Link]((a, b) => {


const itemA = Number([Link](-4)); // "item0001" → 1
const itemB = Number([Link](-4)); // "item0002" → 2
return itemA - itemB;
});

Extract last 4 digits: slice(-4) gets "0001" from "item0001"

Complete Testing Checklist


Products display correctly
Add to cart shows checkmark

Header totals update


Footer totals update

Toggle to cart view


Cart displays items

Quantity dropdown works


Update quantity updates totals

Remove item works


Place order clears cart

Back to products view


Memoization prevents unnecessary re-renders

Images display correctly


Currency formats properly

No console errors

Common Issues & Solutions

Issue: Images not loading in production

Solution: Use new URL() pattern, not require()


// ✅ Correct
const img = new URL(`../images/${sku}.jpg`, [Link]).href;

// ❌ Wrong
const img = require(`../images/${sku}.jpg`);

Issue: Components re-rendering unnecessarily

Solution:

1. Memoize objects in context with useMemo

2. Memoize functions with useCallback

3. Use [Link] with comparison function for list items

Issue: TypeScript error on dynamic object keys

Solution: Use keyof assertion

// ❌ Error
obj[key]

// ✅ Correct
obj[key as keyof ObjectType]

Issue: Optional payload causing type errors

Solution: Use nullish coalescing

// ❌ Error
text: [Link] // Could be undefined

// ✅ Correct
text: [Link] ?? ""

Key Takeaways

Context Best Practices

1. Separate contexts by feature - Products, Cart, Auth, etc.

2. Create custom hooks - Cleaner than importing useContext everywhere


3. Memoize in context - Use useCallback and useMemo

4. Export provider - Let consumers import provider, not internal hook

5. Type everything - Use ReturnType utility for hook types

Performance Best Practices


1. Memoize action objects - Prevents re-renders
2. Wrap functions in useCallback - Maintains referential equality

3. Use [Link] for list items - Prevents unnecessary updates


4. Don't over-memoize - Simple calculations don't need memoization

5. Test with React DevTools - Verify re-render behavior

TypeScript Patterns

// ReturnType utility
type HookType = ReturnType<typeof customHook>;

// keyof assertion for dynamic keys


obj[key as keyof ObjectType]

// Children type (React 18+)


type ChildrenType = {
children?: ReactElement | ReactElement[];
};

// Dispatch type
[Link]<[Link]<T>>
[Link]<ReducerAction>

// Event types
ChangeEvent<HTMLInputElement>
ChangeEvent<HTMLSelectElement>

Vite Patterns

// Dynamic images
new URL(`../images/${name}.jpg`, [Link]).href

// Environment variables
[Link].VITE_API_KEY

// JSON server (dev only)


npx json-server -w data/[Link] -p 3500

Final Project Structure

lesson-16/
├── src/
│ ├── components/
│ │ ├── [Link] # Display totals, nav
│ │ ├── [Link] # Toggle button
│ │ ├── [Link] # Display totals/copyright
│ │ ├── [Link] # Map products
│ │ ├── [Link] # Individual product (memoized)
│ │ ├── [Link] # Cart view
│ │ └── [Link] # Cart item (memoized)
│ ├── context/
│ │ ├── [Link] # Products state
│ │ └── [Link] # Cart state & logic
│ ├── hooks/
│ │ ├── [Link] # Cart context hook
│ │ └── [Link] # Products context hook
│ ├── images/
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── [Link]
│ ├── [Link] # Main component
│ ├── [Link] # Entry point with providers
│ └── [Link] # Styles
├── data/
│ └── [Link] # Product data (dev only)
└── [Link]

Congratulations! 🎉

You've completed a full-featured shopping cart with:

✅ React + TypeScript
✅ Context API for state management
✅ useReducer for complex state logic
✅ Custom hooks for clean code
✅ Performance optimization with [Link]
✅ Type-safe development throughout
✅ Dynamic image handling with Vite
✅ Currency formatting
✅ Proper accessibility attributes
Remember: Progress over perfection. Keep coding! 🚀

Complete TypeScript Tutorial Series - Finished!

You might also like