Arrays & Objects
E-commerce apps are full of lists: product catalogs, cart items, order history. Let's master typing them.
Arrays of Objects
Combine what we learned about arrays and interfaces:
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
// Array of products
const catalog: Product[] = [
{ id: "SKU-001", name: "Wireless Mouse", price: 29.99, inStock: true },
{ id: "SKU-002", name: "USB-C Hub", price: 49.99, inStock: true },
{ id: "SKU-003", name: "Webcam HD", price: 79.99, inStock: false }
];
// TypeScript knows each item is a Product
catalog.forEach(product => {
console.log(`${product.name}: ${product.price}`);
// Autocomplete works! product.na... suggests 'name'
});
Array Methods with Types
TypeScript tracks types through array operations:
const products: Product[] = [
{ id: "SKU-001", name: "Keyboard", price: 99.99, inStock: true },
{ id: "SKU-002", name: "Mouse", price: 29.99, inStock: true },
{ id: "SKU-003", name: "Monitor", price: 299.99, inStock: false }
];
// filter() returns Product[]
const available = products.filter(p => p.inStock);
// TypeScript knows: available is Product[]
// map() returns the mapped type
const names = products.map(p => p.name);
// TypeScript knows: names is string[]
const prices = products.map(p => p.price);
// TypeScript knows: prices is number[]
// find() returns Product | undefined (might not find it!)
const keyboard = products.find(p => p.name === "Keyboard");
// TypeScript knows: keyboard is Product | undefined
if (keyboard) {
console.log(keyboard.price); // Safe to access after check
}
// reduce() - type the accumulator
const totalValue = products.reduce(
(sum, product) => sum + product.price,
0 // Initial value tells TS the return type is number
);
---
Index Signatures
Sometimes you don't know property names ahead of time - like a product inventory by SKU:
// Stock levels by product ID
interface Inventory {
[productId: string]: number; // Any string key -> number value
}
const stock: Inventory = {
"SKU-001": 50,
"SKU-002": 125,
"SKU-003": 0,
"SKU-004": 30
};
// Access dynamically
const sku = "SKU-002";
console.log(`Stock for ${sku}: ${stock[sku]}`); // 125
// Add new entries
stock["SKU-005"] = 75;
Combining with Known Properties
interface ProductMap {
lastUpdated: Date; // Known property
[productId: string]: Product | Date; // Dynamic properties
}
Better approach - use a nested structure:
interface Catalog {
lastUpdated: Date;
products: {
[productId: string]: Product;
};
}
const catalog: Catalog = {
lastUpdated: new Date(),
products: {
"SKU-001": { id: "SKU-001", name: "Mouse", price: 29.99, inStock: true },
"SKU-002": { id: "SKU-002", name: "Keyboard", price: 99.99, inStock: true }
}
};
---
The Record Utility Type
A cleaner way to type objects with dynamic keys:
// Record<KeyType, ValueType>
type Inventory = Record<string, number>;
const stock: Inventory = {
"SKU-001": 50,
"SKU-002": 125
};
// Prices by product ID
type PriceList = Record<string, number>;
const prices: PriceList = {
"SKU-001": 29.99,
"SKU-002": 49.99
};
// Product lookup by ID
type ProductLookup = Record<string, Product>;
const products: ProductLookup = {
"SKU-001": { id: "SKU-001", name: "Mouse", price: 29.99, inStock: true }
};
---
Typing the Shopping Cart
Let's build a complete cart type:
interface Product {
id: string;
name: string;
price: number;
imageUrl?: string;
}
interface CartItem {
product: Product;
quantity: number;
}
interface Cart {
items: CartItem[];
couponCode: string | null;
}
// Example cart
const cart: Cart = {
items: [
{
product: { id: "SKU-001", name: "Wireless Earbuds", price: 79.99 },
quantity: 1
},
{
product: { id: "SKU-002", name: "Phone Case", price: 19.99 },
quantity: 2
}
],
couponCode: "SAVE10"
};
// Calculate totals (TypeScript validates everything)
const subtotal = cart.items.reduce(
(sum, item) => sum + (item.product.price * item.quantity),
0
);
console.log(`Subtotal: ${subtotal.toFixed(2)}`); // $119.97
---
Readonly Arrays
Prevent accidental mutations:
// Immutable product list
const featuredProducts: readonly Product[] = [
{ id: "SKU-001", name: "Best Seller", price: 99.99, inStock: true }
];
featuredProducts.push({ /* ... */ }); // ❌ Error: Property 'push' does not exist on type 'readonly Product[]'
// Alternative syntax
const categories: ReadonlyArray<string> = ["Electronics", "Clothing", "Home"];
categories[0] = "Tech"; // ❌ Error: Index signature in type 'readonly string[]' only permits reading
---
Type Challenges
Challenge 1: Type the Order History
Create types for an order with multiple items:
// Your types here
const order = {
orderId: "ORD-12345",
date: new Date("2024-01-15"),
items: [
{ productName: "Laptop", price: 999, quantity: 1 },
{ productName: "Mouse", price: 29, quantity: 2 }
],
total: 1057,
status: "delivered"
};
Solution
interface OrderItem {
productName: string;
price: number;
quantity: number;
}
interface Order {
orderId: string;
date: Date;
items: OrderItem[];
total: number;
status: string; // We'll improve this with union types!
}
const order: Order = {
orderId: "ORD-12345",
date: new Date("2024-01-15"),
items: [
{ productName: "Laptop", price: 999, quantity: 1 },
{ productName: "Mouse", price: 29, quantity: 2 }
],
total: 1057,
status: "delivered"
};
Challenge 2: Create a Category Map
Type an object where keys are category names and values are arrays of products:
// Your type here
const categoryProducts = {
electronics: [
{ id: "1", name: "Phone", price: 699, inStock: true }
],
clothing: [
{ id: "2", name: "T-Shirt", price: 25, inStock: true },
{ id: "3", name: "Jeans", price: 59, inStock: false }
]
};
Solution
type CategoryProducts = Record<string, Product[]>;
// Or with an interface:
interface CategoryProducts {
[category: string]: Product[];
}
const categoryProducts: CategoryProducts = {
electronics: [
{ id: "1", name: "Phone", price: 699, inStock: true }
],
clothing: [
{ id: "2", name: "T-Shirt", price: 25, inStock: true },
{ id: "3", name: "Jeans", price: 59, inStock: false }
]
};
---
Key Takeaways
| Concept | Syntax | Use Case |
|---|---|---|
| Array of objects | Product[] | Product catalog |
| Index signature | [key: string]: Type | Dynamic keys |
| Record | Record | Cleaner dynamic objects |
| Readonly array | readonly T[] | Immutable lists |
Next up: Typed Functions - adding types to addToCart(), calculateTotal(), and more!