Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Truthiness narrowing in TypeScript is a feature where the type of a variable is narrowed based on whether it is truthy or falsy in a conditional statement. This allows TypeScript to infer more specific types within certain code blocks, improving type safety and enabling better tooling support.

Let’s break it down step by step:

1. Truthy and Falsy Values in JavaScript/TypeScript

In JavaScript and TypeScript, values can be evaluated in a boolean context (e.g., in an if statement or a logical operation). Some values are considered falsy, and everything else is truthy.

  • Falsy values:

    • false
    • 0
    • ”” (empty string)
    • null
    • undefined
    • NaN
  • Truthy values:

    • Everything else (e.g., true, 1, ”hello”, [], {}, non-empty strings, etc.).

2. Type Narrowing in TypeScript

Type narrowing is the process by which TypeScript infers a more specific type for a variable within a certain scope, based on certain conditions or checks. For example:

  • If you check whether a variable is null or undefined, TypeScript can narrow its type to exclude those possibilities in the subsequent code.

Truthiness narrowing is a specific form of type narrowing where the type is narrowed based on whether the value is truthy or falsy.

3. How Truthiness Narrowing Works

When you use a value in a conditional statement (like an if or &&), TypeScript can narrow the type of that value based on whether it is truthy or falsy.

Example 1: Narrowing string | null

Function printLength(str: string | null) {
  If (str) {
    // Here, `str` is narrowed to `string` because `null` is falsy
    Console.log(str.length); // Safe to access `length`
  } else {
    // Here, `str` is narrowed to `null`
    Console.log(“String is null or empty”);
  }
}

In this example:

  • Inside the if block, str is narrowed to string because null is falsy.
  • In the else block, str is narrowed to null.

Example 2: Narrowing number | undefined

Function printValue(num: number | undefined) {
  If (num) {
    // Here, `num` is narrowed to `number` (and not `0`)
    Console.log(`The value is ${num}`);
  } else {
    // Here, `num` is narrowed to `undefined` or `0`
    Console.log(“Value is undefined or zero”);
  }
}

In this example:

  • Inside the if block, num is narrowed to number (excluding 0 because 0 is falsy).
  • In the else block, num is narrowed to undefined or 0.

Example 3: Narrowing with Logical Operators

Function greet(name: string | null) {
  Const displayName = name || “Guest”; // If `name` is falsy, use “Guest”
  Console.log(`Hello, ${displayName}`);
}

Here:

  • The || operator checks if name is falsy. If it is, ”Guest” is used instead.
  • TypeScript understands that displayName will always be a string because || ensures a fallback.

4. Limitations of Truthiness Narrowing

While truthiness narrowing is powerful, it has some limitations:

  • It doesn’t distinguish between different falsy values. For example, null, undefined, and ”” are all treated the same.
  • It doesn’t work well with 0 or NaN if you want to treat them as valid values (since they are falsy).

Example: Problem with 0

Function printNumber(num: number | null) {
  If (num) {
    Console.log(`Number is ${num}`);
  } else {
    Console.log(“Number is null or zero”); // `0` is treated as falsy
  }
}

In this case:

  • If num is 0, it will fall into the else block, which might not be the desired behavior.

5. Explicit Checks for Better Narrowing

To avoid issues with falsy values like 0 or ””, you can use explicit checks (e.g., === null or === undefined).

Example: Explicit Check for null

Function printValue(value: string | null) {
  If (value !== null) {
    // Here, `value` is narrowed to `string`
    Console.log(value.toUpperCase());
  } else {
    // Here, `value` is narrowed to `null`
    Console.log(“Value is null”);
  }
}

6. Truthiness Narrowing with !! Operator

The !! operator can be used to explicitly convert a value to a boolean, which can help with truthiness narrowing.

Example:

Function checkValue(value: string | null) {
  Const hasValue = !!value; // Converts `value` to a boolean
  If (hasValue) {
    // Here, `value` is narrowed to `string`
    Console.log(`Value is ${value}`);
  }
}

7. Truthiness Narrowing in while and for Loops

Truthiness narrowing also works in loops.

Example:

Function processArray(arr: number[] | null) {
  While (arr) {
    // Here, `arr` is narrowed to `number[]`
    Console.log(arr.pop());
    If (arr.length === 0) {
      Arr = null; // Exit the loop
    }
  }
}

Summary

  • Truthiness narrowing is a TypeScript feature that narrows the type of a variable based on whether it is truthy or falsy in a conditional statement.
  • It works with if, else, while, for, and logical operators like || and &&.
  • Be cautious with falsy values like 0, ””, and NaN, as they might lead to unintended behavior.
  • Use explicit checks (e.g., === null or !== undefined) for more precise narrowing.

This feature makes TypeScript more powerful and expressive, allowing you to write safer and more predictable code.

Additional concepts and best practices related to truthiness narrowing and fallback values in TypeScript.

4. Truthiness Narrowing in Real-World Scenarios

Truthiness narrowing is not just a theoretical concept—it’s widely used in real-world TypeScript code. Here are some practical scenarios where this pattern is useful:

Scenario 1: Handling Optional Function Parameters

When a function parameter is optional (or nullable), you can use truthiness narrowing to provide a default value.

Function createUser(name?: string) {
  Const username = name || “Anonymous”; // Default to “Anonymous” if `name` is falsy
  Console.log(`User created: ${username}`);
}

createUser(); // Output: User created: Anonymous
createUser(“Alice”); // Output: User created: Alice
  • If name is undefined (or any other falsy value), the fallback ”Anonymous” is used.

Scenario 2: Configuring Objects with Default Values

When working with configuration objects, you can use truthiness narrowing to ensure that required properties have default values.

Interface Config {
  Timeout?: number;
  Retries?: number;
}

Function initialize(config: Config) {
  Const timeout = config.timeout || 5000; // Default to 5000 if `timeout` is falsy
  Const retries = config.retries || 3; // Default to 3 if `retries` is falsy

  Console.log(`Timeout: ${timeout}, Retries: ${retries}`);
}

Initialize({}); // Output: Timeout: 5000, Retries: 3
Initialize({ timeout: 1000 }); // Output: Timeout: 1000, Retries: 3
  • If timeout or retries are falsy (e.g., 0, null, undefined), the fallback values are used.

Scenario 3: Rendering UI Components

In frontend frameworks like React, truthiness narrowing is often used to conditionally render components or provide default values for props.

Function WelcomeMessage({ username }: { username?: string }) {
  Const displayName = username || “Guest”;
  Return <h1>Welcome, {displayName}!</h1>;
}

// Usage
<WelcomeMessage />; // Renders: Welcome, Guest!
<WelcomeMessage username=”Alice” />; // Renders: Welcome, Alice!
  • If username is falsy, the fallback ”Guest” is used.

5. Potential Pitfalls and How to Avoid Them

While truthiness narrowing is powerful, it can lead to unintended behavior if not used carefully. Here are some common pitfalls and how to avoid them:

Pitfall 1: Treating 0 or ”” as Invalid

The || operator treats 0 and ”” as falsy, which might not always be desired.

Example:

Function printValue(value: number | null) {
  Const displayValue = value || “Unknown”; // Fallback if `value` is falsy
  Console.log(`Value: ${displayValue}`);
}

printValue(0); // Output: Value: Unknown (might not be desired)

Solution: Use the nullish coalescing operator (??) to only check for null or undefined.

Function printValue(value: number | null) {
  Const displayValue = value ?? “Unknown”; // Fallback only if `value` is `null` or `undefined`
  Console.log(`Value: ${displayValue}`);
}

printValue(0); // Output: Value: 0

Pitfall 2: Overlooking NaN

NaN is also falsy, so it will trigger the fallback when using ||.

Example:

Function calculateTotal(price: number, discount: number) {
  Const finalPrice = price  discount || 0; // Fallback if `price – discount` is falsy
  Console.log(`Final Price: ${finalPrice}`);
}

calculateTotal(100, “invalid”); // Output: Final Price: 0 (because `NaN` is falsy)

Solution: Validate inputs explicitly before performing calculations.

Function calculateTotal(price: number, discount: number) {
  If (typeof price !== “number” || typeof discount !== “number”) {
    Throw new Error(“Invalid input”);
  }
  Const finalPrice = price  discount;
  Console.log(`Final Price: ${finalPrice}`);
}

Pitfall 3: Overusing Truthiness Narrowing

Overusing truthiness narrowing can make the code harder to read and maintain, especially when dealing with complex conditions.

Example:

Function processData(data: string | null | undefined) {
  Const processedData = data || “default” || getFallback() || “unknown”;
  Console.log(processedData);
}

Solution: Break down complex conditions into smaller, more readable steps.

Function processData(data: string | null | undefined) {
  Let processedData = data ?? “default”;
  If (!processedData) {
    processedData = getFallback() || “unknown”;
  }
  Console.log(processedData);
}

6. Best Practices for Truthiness Narrowing

  1. Use ?? for null or undefined Checks:

    • Prefer the nullish coalescing operator (??) when you only want to check for null or undefined.
  2. Be Explicit with Falsy Values:

    • If you need to handle 0, ””, or NaN differently, use explicit checks (e.g., === 0, === “”).
  3. Avoid Overusing ||:

    • Use || only when you want to handle all falsy values. For more specific checks, use ?? or explicit conditions.
  4. Combine with Optional Chaining:

    • Use optional chaining (?.) to safely access nested properties before applying truthiness narrowing.

    Example:

    Function getUserName(user?: { name?: string }) {
      Const name = user?.name || “Guest”;
      Console.log(`Hello, ${name}`);
    }
    
    getUserName(); // Output: Hello, Guest
    getUserName({ name: “Alice” }); // Output: Hello, Alice

7. Advanced Example: Combining Truthiness Narrowing with Type Guards

You can combine truthiness narrowing with type guards to create more robust and type-safe code.

Example:

Function processInput(input: string | number | null) {
  If (typeof input === “string”) {
    Console.log(`Input is a string: ${input.trim()}`);
  } else if (typeof input === “number”) {
    Console.log(`Input is a number: ${input.toFixed(2)}`);
  } else {
    Console.log(“Input is null or invalid”);
  }
}

processInput(  Hello  ); // Output: Input is a string: Hello
processInput(42); // Output: Input is a number: 42.00
processInput(null); // Output: Input is null or invalid
  • Here, typeof checks are used to narrow the type of input before performing operations.

Summary of Key Points

  • Truthiness narrowing is a powerful TypeScript feature that allows you to narrow types based on whether a value is truthy or falsy.
  • The || operator is commonly used for fallback values, but it treats all falsy values (0, ””, null, undefined, NaN) the same.
  • Use the nullish coalescing operator (??) to only check for null or undefined.
  • Be cautious with falsy values like 0 and ””, and use explicit checks when necessary.
  • Combine truthiness narrowing with type guards and optional chaining for more robust and readable code.

Let me know if you’d like to explore more examples or dive deeper into any specific aspect!

About

A comprehensive Type folder, consisting of several TypeScript file that i use in keanring

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages