TypeScript Array Map
When working with arrays in TypeScript, the map
method is a vital tool for changing and handling data. While most developers know the basics, the nuances of type safety and advanced applications can make this method even more valuable. In this article, we'll look at the advanced uses and type safety aspects of the map
method, showing how to use it effectively in your TypeScript projects.
Transforming Elements with TypeScript's Array Map Method
The TypeScript array map
method applies a function to each element in an array and returns a new array with the transformed values:
const numbers = [1, 2, 3, 4, 5];
const doubleNumbers = numbers.map((num) => num * 2);
console.log(doubleNumbers); // [2, 4, 6, 8, 10]
The function passed to map
takes each array element as an argument and returns a transformed value. The original array remains unchanged while a new array is created with the transformed values.
Using map
is more concise than traditional loops when you need to transform each element in an array. It's particularly useful when working with TypeScript generics, as these ensure type safety throughout the transformation.
Using TypeScript Types for Better Type Safety
TypeScript types enhance map
operations with compile-time checking. When converting data types, you can specify the expected output type:
const stringNumbers: string[] = ['1', '2', '3', '4', '5'];
const numberArray: number[] = stringNumbers.map((str) => parseInt(str));
console.log(numberArray); // [1, 2, 3, 4, 5]
You can also use TypeScript interface definitions for more complex transformations:
interface User {
id: number;
name: string;
age: number;
}
interface UserSummary {
id: number;
displayName: string;
}
const users: User[] = [
{ id: 1, name: 'John', age: 28 },
{ id: 2, name: 'Jane', age: 32 }
];
const mapToSummary = (user: User): UserSummary => ({
id: user.id,
displayName: `${user.name} (${user.age})`
});
const userSummaries: UserSummary[] = users.map(mapToSummary);
This approach provides better code completion and catches type errors during development. For more complex type requirements, you can incorporate TypeScript utility types to make your transformations more flexible. When working with filtered data in database applications, Complex Filters in Convex offers patterns that pair well with array transformations.
Handling Undefined and Null Values in Array Map
When working with data that might contain undefined or null values, use optional chaining or nullish coalescing operators with the TypeScript foreach method's counterpart, map
:
const users = [
{ id: 1, name: 'John' },
{ id: 2 },
{ id: 3, name: 'Jane' },
];
const usernames = users.map((user) => user.name ?? 'Unknown');
console.log(usernames); // ['John', 'Unknown', 'Jane']
The nullish coalescing operator (??
) provides a fallback value when the left operand is null
or undefined
. This pattern is common when mapping over API responses or database results where some fields might be missing.
For more advanced pattern matching, you can use TypeScript typeof checks within your mapping function:
type Item = string | number | { value: number };
const items: Item[] = ['apple', 42, { value: 100 }];
const values = items.map((item) => {
if (typeof item === 'string') return item.toUpperCase();
if (typeof item === 'number') return item * 2;
return item.value;
});
console.log(values); // ['APPLE', 84, 100]
When building complex web applications, you can use these techniques alongside Convex's API generation to safely transform data coming from various sources.
Mapping Arrays of Objects to New Structures
When dealing with complex data structures, the TypeScript filter method's cousin map
truly shines. You can transform arrays of objects into completely new formats:
const users = [
{ id: 1, name: 'John', age: 25 },
{ id: 2, name: 'Jane', age: 30 },
];
const userSummaries = users.map((user) => ({
id: user.id,
name: user.name,
ageGroup: user.age > 25 ? 'Adult' : 'Young Adult',
}));
console.log(userSummaries);
// [
// { id: 1, name: 'John', ageGroup: 'Young Adult' },
// { id: 2, name: 'Jane', ageGroup: 'Adult' },
// ]
This technique is common when preparing data for display components or API responses. For more complex object manipulations, combine map
with object type definitions to maintain type safety:
interface User {
id: number;
name: string;
age: number;
}
interface UserDisplay {
id: number;
name: string;
ageGroup: string;
profile?: string;
}
const users: User[] = [
{ id: 1, name: 'John', age: 25 },
{ id: 2, name: 'Jane', age: 30 },
];
const mapToDisplay = (user: User): UserDisplay => ({
id: user.id,
name: user.name,
ageGroup: user.age > 25 ? 'Adult' : 'Young Adult',
});
const displayUsers: UserDisplay[] = users.map(mapToDisplay);
When working with TypeScript in a Convex project, these transformations work well with TypeScript tag patterns on their Stack blog for building type-safe applications.
Transforming Data Structures with Array Map
To reshape data using map
, convert an array of objects to a new structure:
const users = [
{ id: 1, name: 'John', age: 25 },
{ id: 2, name: 'Jane', age: 30 },
];
const userIds = users.map((user) => user.id);
console.log(userIds); // [1, 2]
This technique is useful when you need to extract a specific property from an array of objects, such as collecting IDs for database queries or filters. You can also use map
with TypeScript data types to flatten nested structures:
const nestedData = [
{ user: { id: 1, name: 'John' }, permissions: ['read', 'write'] },
{ user: { id: 2, name: 'Jane' }, permissions: ['read'] },
];
const simplifiedUsers = nestedData.map(item => ({
id: item.user.id,
name: item.user.name,
canWrite: item.permissions.includes('write')
}));
console.log(simplifiedUsers);
// [
// { id: 1, name: 'John', canWrite: true },
// { id: 2, name: 'Jane', canWrite: false }
// ]
When working with collection transformations, consider using the TypeScript reduce method alongside map
for more complex data shaping operations.
Converting Data Types within an Array
The map
method excels at transforming array elements from one data type to another. This is particularly useful when working with TypeScript string to number conversions:
const stringBools: string[] = ['true', 'false', 'true'];
const boolArray: boolean[] = stringBools.map((str) => str === 'true');
console.log(boolArray); // [true, false, true]
You can also convert numbers to formatted strings with TypeScript number to string conversions:
const prices: number[] = [19.99, 24.5, 9.95];
const formattedPrices: string[] = prices.map((price) => `$${price.toFixed(2)}`);
console.log(formattedPrices); // ['$19.99', '$24.50', '$9.95']
For more complex scenarios where you need to parse input strings to numeric values, you can use TypeScript convert string to number techniques:
const mixedInputs: string[] = ['42', '3.14', 'invalid', '100'];
const parsedValues: (number | null)[] = mixedInputs.map((input) => {
const parsed = parseFloat(input);
return isNaN(parsed) ? null : parsed;
});
console.log(parsedValues); // [42, 3.14, null, 100]
These type conversion techniques are especially helpful when processing user input or API responses that come in as strings but need to be used as numeric values in calculations.
Applying Custom Logic within Array Map
To use custom logic in the map
function, apply conditional statements or functions:
const orders = [
{ id: 1, total: 100, status: 'pending' },
{ id: 2, total: 200, status: 'shipped' },
{ id: 3, total: 300, status: 'pending' },
];
const orderStatuses = orders.map((order) => {
if (order.status === 'pending') {
return `Order #${order.id} is pending`;
} else {
return `Order #${order.id} has been ${order.status}`;
}
});
console.log(orderStatuses);
// [
// 'Order #1 is pending',
// 'Order #2 has been shipped',
// 'Order #3 is pending',
// ]
For formatting text efficiently, you can use TypeScript string interpolation within the map callback. This creates cleaner, more readable code than traditional string concatenation:
const products = [
{ name: 'Laptop', price: 999.99, inStock: true },
{ name: 'Phone', price: 699.99, inStock: false },
{ name: 'Tablet', price: 399.99, inStock: true }
];
const productInfo = products.map(product => {
const stockStatus = product.inStock ? 'In Stock' : 'Out of Stock';
return `${product.name}: $${product.price} - ${stockStatus}`;
});
You can also handle optional properties safely using TypeScript optional chaining inside your map callbacks:
const complexData = [
{ user: { name: 'Alice', address: { city: 'New York' } } },
{ user: { name: 'Bob' } },
{ user: { name: 'Charlie', address: { city: 'Boston' } } }
];
const cities = complexData.map(item => item.user?.address?.city ?? 'Unknown');
console.log(cities); // ['New York', 'Unknown', 'Boston']
When working with complex transformation functions, consider extracting the logic into a standalone TypeScript function for better readability and reusability:
function processOrder(order: Order): string {
const { id, status, total } = order;
const formattedTotal = `$${total.toFixed(2)}`;
return status === 'pending'
? `Order #${id} (${formattedTotal}) is awaiting processing`
: `Order #${id} (${formattedTotal}) has been ${status}`;
}
const orderSummaries = orders.map(processOrder);
Final Thoughts on TypeScript Array Map
The array map
method is one of TypeScript's most versatile tools for data transformation. By applying type safety and custom logic, you can build robust transformation pipelines that maintain code quality and catch errors early. Whether you're extracting values, reshaping objects, or converting between types, map
offers a clean, functional approach to common programming tasks.
For more pattern examples, check out our comprehensive guide on TypeScript map, which covers additional techniques and best practices.