================================================================================ TYPESCRIPT FUNDAMENTALS - CHALLENGE 1 SOLUTION Chapter 7: Advanced Types Challenge: Type Guard Function ================================================================================ PROBLEM: Create a type guard function that checks if a value is a string. Use it in a function that only proceeds if the guard passes. SOLUTION: ================================================================================ // Type guard function - returns boolean and uses 'is' keyword function isString(value: unknown): value is string { return typeof value === "string"; } // Function that uses the type guard function processString(input: unknown): string { if (isString(input)) { // Inside this block, TypeScript knows 'input' is definitely a string return input.toUpperCase(); } else { return "Input was not a string"; } } // Testing the solution console.log(processString("hello")); // "HELLO" console.log(processString(42)); // "Input was not a string" console.log(processString(true)); // "Input was not a string" console.log(processString(null)); // "Input was not a string" ================================================================================ WHY THIS WORKS: ================================================================================ 1. Type Guard Syntax: "value is string" - The "is" keyword is TypeScript-specific - It tells TypeScript: "If this function returns true, the parameter is definitely of this type" - Without "is", TypeScript would just see it as a boolean return type 2. Type Narrowing - BEFORE guard: input is "unknown" (could be anything) - AFTER guard passes: input is narrowed to "string" - This narrowing only happens inside the if block 3. Why "unknown" instead of "any" - "unknown" is safer - you must check the type before using it - "any" bypasses all type checking (bad practice) ================================================================================ ALTERNATIVE APPROACHES: ================================================================================ // Approach 1: Using typeof directly (no custom guard) function processString(input: unknown): string { if (typeof input === "string") { return input.toUpperCase(); } return "Input was not a string"; } // Approach 2: Generic type guard factory function createTypeGuard(typeName: string) { return (value: unknown): value is T => { return typeof value === typeName; }; } const isString2 = createTypeGuard("string"); const isNumber = createTypeGuard("number"); // Approach 3: For arrays, use Array.isArray() function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every(item => typeof item === "string"); } const mixed: unknown[] = ["a", "b", "c"]; if (isStringArray(mixed)) { console.log(mixed.map(s => s.toUpperCase())); // Safe! } ================================================================================ REAL-WORLD EXAMPLES: ================================================================================ Example 1: API Response Handler type JSONValue = string | number | boolean | null | JSONValue[]; function isJSONObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } function parseAPIResponse(data: unknown) { if (isJSONObject(data)) { // Now we can safely access properties console.log(data.name, data.age); } else { console.log("Invalid response format"); } } Example 2: Error Handling function isError(value: unknown): value is Error { return value instanceof Error; } function handleException(e: unknown) { if (isError(e)) { console.log("Error message:", e.message); console.log("Stack:", e.stack); } else { console.log("Unknown error:", e); } } Example 3: Form Input Validation type ValidEmail = string & { readonly __brand: "ValidEmail" }; function isValidEmail(value: unknown): value is ValidEmail { return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); } function submitForm(email: unknown) { if (isValidEmail(email)) { // email is now a ValidEmail - we can trust it sendEmail(email); } else { console.log("Please enter a valid email"); } } ================================================================================ KEY TAKEAWAYS: ================================================================================ ✅ Type guards use the "is" keyword for type narrowing ✅ They're especially useful with "unknown" type (safe default) ✅ Custom type guards are more readable than repeated typeof checks ✅ Type narrowing only applies within the conditional block ✅ Use for form validation, API responses, and error handling ✅ Can combine with instanceof for class instances ================================================================================ TESTING & VERIFICATION: ================================================================================ // Test cases to verify your solution const testCases: unknown[] = [ "hello", 42, { name: "test" }, ["a", "b"], null, undefined, true, Symbol("sym") ]; testCases.forEach(test => { if (isString(test)) { console.log(`✅ ${test} is a string`); } else { console.log(`❌ ${test} is NOT a string`); } }); Expected output: ✅ hello is a string ❌ 42 is NOT a string ❌ [object Object] is NOT a string ❌ a,b is NOT a string ❌ null is NOT a string ❌ undefined is NOT a string ❌ true is NOT a string ❌ Symbol(sym) is NOT a string ================================================================================