Skip to main content

TypeScript Functions

Functions are one of the fundamental building blocks in JavaScript and TypeScript. TypeScript enhances JavaScript functions by adding type annotations, making your code more predictable and easier to debug. In this guide, we'll explore how to define and use functions in TypeScript, with a special focus on patterns that are useful in Next.js applications.

Function Basics in TypeScript

In TypeScript, you can define functions with explicit type annotations for parameters and return values. This helps catch errors during development rather than at runtime.

Basic Function Declaration

Here's a simple function with type annotations:

typescript
function greet(name: string): string {
return `Hello, ${name}!`;
}

// Usage
const greeting = greet("NextJS Developer");
console.log(greeting); // Output: Hello, NextJS Developer!

Let's break down the components:

  • name: string - The parameter name must be a string
  • : string after the parameter list - The function returns a string

Function Parameters

TypeScript provides several ways to work with function parameters.

Optional Parameters

You can make parameters optional by adding a question mark ?:

typescript
function buildProfile(firstName: string, lastName: string, age?: number): string {
if (age) {
return `${firstName} ${lastName}, ${age} years old`;
} else {
return `${firstName} ${lastName}`;
}
}

// Both are valid
console.log(buildProfile("John", "Doe", 30)); // Output: John Doe, 30 years old
console.log(buildProfile("Jane", "Smith")); // Output: Jane Smith

Default Parameters

You can provide default values for parameters:

typescript
function createGreeting(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}

console.log(createGreeting("Next.js Developer")); // Output: Hello, Next.js Developer!
console.log(createGreeting("TypeScript Expert", "Welcome")); // Output: Welcome, TypeScript Expert!

Rest Parameters

Collect multiple arguments into an array:

typescript
function sumNumbers(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}

console.log(sumNumbers(1, 2, 3, 4, 5)); // Output: 15
console.log(sumNumbers(10, 20)); // Output: 30

Function Return Types

TypeScript allows you to be explicit about what your functions return.

Basic Return Types

typescript
function multiply(a: number, b: number): number {
return a * b;
}

function logMessage(message: string): void {
console.log(message);
// No return value needed
}

function fail(message: string): never {
throw new Error(message);
// This function never returns normally
}

Union Return Types

Sometimes a function might return different types:

typescript
function getFirstElement(arr: any[]): any | undefined {
return arr.length > 0 ? arr[0] : undefined;
}

// More specific with generics (we'll cover this more later)
function getFirstItem<T>(arr: T[]): T | undefined {
return arr.length > 0 ? arr[0] : undefined;
}

const first = getFirstItem([1, 2, 3]); // Type inferred as number | undefined
console.log(first); // Output: 1

const firstString = getFirstItem(["a", "b", "c"]); // Type inferred as string | undefined
console.log(firstString); // Output: "a"

Function Types

TypeScript allows you to define function types, which is useful for callbacks and function references.

Function Type Expressions

typescript
type GreetFunction = (name: string) => string;

const sayHello: GreetFunction = (name) => {
return `Hello, ${name}!`;
};

const sayHi: GreetFunction = (name) => {
return `Hi, ${name}!`;
};

console.log(sayHello("TypeScript")); // Output: Hello, TypeScript!
console.log(sayHi("Next.js")); // Output: Hi, Next.js!

Arrow Functions in TypeScript

Arrow functions work just like in JavaScript but with type annotations:

typescript
// Arrow function with explicit type
const add = (a: number, b: number): number => a + b;

// Usage
console.log(add(5, 3)); // Output: 8

// Arrow function as parameter
function executeCalculation(
a: number,
b: number,
calculation: (x: number, y: number) => number
): number {
return calculation(a, b);
}

const sum = executeCalculation(10, 20, (x, y) => x + y);
console.log(sum); // Output: 30

const product = executeCalculation(10, 20, (x, y) => x * y);
console.log(product); // Output: 200

Function Overloads

Function overloads allow you to define multiple function signatures for the same function:

typescript
// Overload signatures
function convertValue(value: string): number;
function convertValue(value: number): string;
function convertValue(value: boolean): string;

// Implementation signature
function convertValue(value: string | number | boolean): string | number {
if (typeof value === 'string') {
return parseInt(value, 10);
} else if (typeof value === 'number') {
return value.toString();
} else {
return value ? "true" : "false";
}
}

const stringToNum = convertValue("42"); // Type: number
console.log(stringToNum); // Output: 42

const numToString = convertValue(100); // Type: string
console.log(numToString); // Output: "100"

const boolToString = convertValue(true); // Type: string
console.log(boolToString); // Output: "true"

Practical Application in Next.js

Let's explore some common function patterns in Next.js applications:

API Request Functions

typescript
// A common pattern for API calls in Next.js applications
async function fetchUserData(userId: string): Promise<User> {
try {
const response = await fetch(`/api/users/${userId}`);

if (!response.ok) {
throw new Error('Failed to fetch user data');
}

const data: User = await response.json();
return data;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}

// Usage in a component
const UserProfile = ({ userId }: { userId: string }) => {
const [user, setUser] = useState<User | null>(null);

useEffect(() => {
const loadUser = async () => {
try {
const userData = await fetchUserData(userId);
setUser(userData);
} catch (error) {
// Handle error
}
};

loadUser();
}, [userId]);

// ...rest of component
};

Event Handler Functions

typescript
// Event handler with TypeScript
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
// Form submission logic
};

// Click handler with specific event type
const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
console.log('Button clicked at:', e.clientX, e.clientY);
};

// Input change handler
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target;
// Update state, etc.
};

Helper Functions

typescript
// Format currency - pure function, easy to test
function formatCurrency(amount: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
}

// Data transformer function
function transformUserData(rawUser: any): User {
return {
id: rawUser.id,
fullName: `${rawUser.firstName} ${rawUser.lastName}`,
email: rawUser.email,
isAdmin: Boolean(rawUser.role === 'admin'),
};
}

// Example usage
const price = formatCurrency(29.99);
console.log(price); // Output: $29.99

Best Practices for TypeScript Functions

  1. Be explicit about return types:

    typescript
    // Good: Explicit return type
    function calculateTotal(items: CartItem[]): number {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    }
  2. Use more specific types instead of any:

    typescript
    // Avoid
    function processData(data: any): any {
    // ...
    }

    // Better
    function processData(data: UserData): ProcessedUserData {
    // ...
    }
  3. Use function type declarations for consistency:

    typescript
    type ValidationFunction = (value: string) => boolean;

    const isEmail: ValidationFunction = (value) => {
    // Email validation logic
    return /\S+@\S+\.\S+/.test(value);
    };

    const isRequired: ValidationFunction = (value) => {
    return value.trim().length > 0;
    };
  4. Consider using generics for reusable functions:

    typescript
    function firstOrNull<T>(array: T[]): T | null {
    return array.length > 0 ? array[0] : null;
    }

Summary

In this guide, we covered:

  • Basic function syntax in TypeScript
  • Function parameters (optional, default, rest)
  • Function return types
  • Function types and arrow functions
  • Function overloads
  • Practical function patterns in Next.js applications
  • Best practices for TypeScript functions

TypeScript functions enhance JavaScript functions by adding static type checking, enabling better tooling, safer refactoring, and a more productive development experience. When building Next.js applications, leveraging TypeScript's function capabilities can help you write more robust code with fewer bugs.

Additional Resources

Exercises

  1. Create a function that takes an array of products and returns the total price after applying a discount percentage.
  2. Write a generic function that can filter an array based on a predicate function.
  3. Define a set of functions for form validation (email, required field, minimum length) using function types.
  4. Create a custom hook function in TypeScript that fetches and caches data from an API.
  5. Implement a higher-order function that adds logging before and after a function executes.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)