# Common errors while working with TypeScript - Natalia ## 2.2. How to fix error "Type X is not assignable to type Y" Understanding "Type X is not assignable to type Y" - This error message appears when TypeScript's type checker detects a mismatch between the expected type and the actual type of a variable, parameter, or return value. - The error is a signal that your code might not behave as expected at runtime, and TypeScript is helping you catch this issue early. ### 2.2.1. Mismatched Types Attempting to assign a value of one type to a variable of another type. ```typescript let jonSnowAge: number = "twenty-five"; // Error: Type 'string' is not assignable to type 'number'. ``` Interpret and Fix: Jon Snow's age should be a number, not a string. ```typescript let jonSnowAge: number = 25; // Corrected ``` ### 2.2.2. Object Shape Mismatch The assigned object doesn't match the expected shape ```typescript interface Character { name: string; house: string; } let tyrion: Character = { name: "Tyrion Lannister", title: "Hand of the Queen" }; // Error: Object literal may only specify known properties, // and 'title' does not exist in type 'Character' ``` Interpret and Fix: Jon Snow's age should be a number, not a string. Depends on your design, it can either be fixed in one of the following ways: ```typescript //1. Adjust Object let tyrion: Character = { name: "Tyrion Lannister", house: "Lannister" }; //2. Update Interface: interface Character { name: string; house: string; title?: string; // Make title an optional property } ``` ### 2.2.3. Function Type Mismatch Mismatch between function parameters or return types and their declared types ```typescript function getNumberOfDragons(): number { return "Drogon, Rhaegal, Viserion"; } // Error: Type 'string' is not assignable to type 'number'. ``` Interpret and Fix: Correct the function to return the number of dragons, not their names. ```typescript function getNumberOfDragons(): number { return 3; // Corrected } ``` ### 2.2.4. Array and Tuple Mismatch Assigning an array or tuple to a type that expects a different structure or element types. ```typescript let starkChildren: [string, string, string, string] = ["Robb", "Sansa", "Arya", 12]; // Error: Type 'number' is not assignable to type 'string'. ``` Interpret and Fix: Ensure all elements in the tuple are of the correct type, correcting Arya's age to her name. ```typescript let starkChildren: [string, string, string, string] = ["Robb", "Sansa", "Arya", "Bran"]; // Corrected ``` ### 2.2.5. Enum Mismatch Assigning a value to an enum type that does not exist in the enum. ```typescript enum House { Stark, Lannister, Targaryen } let jonHouse: House = House['Baratheon']; // Error: Property 'Baratheon' does not exist on type 'typeof House'. ``` Interpret and Fix: The House enum does not include `Baratheon`. Ensure you're assigning a value from the defined enum options. ```typescript let jonHouse: House = House.Stark; //or let daenerysHouse: House = House["Targaryen"]; ``` ### 2.2.6. Union Type Mismatch Assigning a value that doesn't fit any type in a union. ```typescript type Weapon = 'Sword' | 'Dagger' | 'Bow'; let jonSnowWeapon: Weapon = 'Axe'; // Error: Type '"Axe"' is not assignable to type 'Weapon'. ``` Interpret and Fix: Ensure the value matches one of the union's types. ```typescript let jonSnowWeapon: Weapon = 'Sword'; ``` ### 2.2.7. Incorrect Generic Types Using a generic type that does not conform to the constraints or expected structure. ```typescript interface Character { name: string; house: string; } function getCharacterDetail<T extends Character>(detail: T): string { return detail.name; } const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" }); // Error: Argument of type '{ name: string; weapon: string; }' is not assignable to parameter of type 'Character'. ``` Interpret and Fix: Adjust the input to match the Character interface or extend the interface to include additional properties. There are a few ways to fix depend on your use cases. - Mark house as optional: ```typescript interface Character { name: string; house?: string; // Making house optional weapon: string; // Adding weapon as an optional property } function getCharacterDetail<T extends Character>(detail: T): string { return detail.name; } const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" }); ``` - Mark house as optional: ```typescript interface Character { name: string; house: string; weapon: string; } // Adjusting the function to accept a type that is part of Character but without enforcing 'house' function getCharacterDetail<T extends Partial<Character> & Pick<Character, 'name' | 'weapon'>>(detail: T): string { return detail.name; } const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" }); ``` - Overloading the Function: ```typescript interface Character { name: string; house: string; weapon: string; } // Overload signature for characters without a house function getCharacterDetail(detail: Omit<Character, 'house'>): string; // Original function signature function getCharacterDetail(detail: Character): string; // Function implementation function getCharacterDetail(detail: any): string { return detail.name; } const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" }); ``` ## 2.3. How to fix error "expression of type string cannot be used to index" **Challenge** ```typescript const allegiances = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; // Initial attempt that causes TypeScript to complain const getAllegiance = (character: string) => { return allegiances[character]; // Error:expression of type 'string' can't be used to index type '{ "Jon Snow": string; "Tyrion Lannister": string; }'. }; ``` ### 2.3.1. Define a Function with a Specific Key Type ```typescript const allegiances = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; const getAllegianceByKey = (character: keyof typeof allegiances) => { return allegiances[character]; // Correct }; console.log(getAllegianceByKey("Jon Snow")); // "Stark" ``` ### 2.3.2. Type Assertion within a Function ```typescript const allegiances = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; const getAllegianceWithAssertion = (character: string) => { return allegiances[character as keyof typeof allegiances]; // Correct }; console.log(getAllegianceWithAssertion("Tyrion Lannister")); // "Lannister" ``` ### 2.3.3. Type Guard to Ensure Key Validity ```typescript const allegiances = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; const isKeyOfAllegiance = (key: any): key is keyof typeof allegiances => { return key in allegiances; } const getAllegianceWithGuard = (character: string) => { if (isKeyOfAllegiance(character)) { return allegiances[character]; //Correct } return undefined; // Or handle the error as appropriate }; console.log(getAllegianceWithGuard("Jon Snow")); // "Stark" ``` ### 2.3.4. Index Signature for Flexible Object Indexing ```typescript const allegiances = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; const allegiancesWithIndex: { [key: string]: string } = { "Jon Snow": "Stark", "Tyrion Lannister": "Lannister" }; const getAllegianceWithIndex = (character: string) => { return allegiancesWithIndex[character]; // Correct }; console.log(getAllegianceWithIndex("Tyrion Lannister")); // "Lannister" ``` ### Another example: ```typescript const weapons = { "Arya Stark": "Needle", "Brienne of Tarth": "Oathkeeper" }; const getWeapon = (hero: string) => { // TypeScript error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ "Arya Stark": string; "Brienne of Tarth": string; }'. return weapons[hero]; }; // Specific Key Type const getAllegianceByKey = (character: keyof typeof allegiances) => allegiances[character]; // Type Assertion const getAllegianceWithAssertion = (character: string) => allegiances[character as keyof typeof allegiances]; // Type Guard function isCharacter(character: any): character is keyof typeof allegiances { return character in allegiances; } const getAllegianceWithGuard = (character: string) => isCharacter(character) ? allegiances[character] : 'Unknown character'; // Index Signature const allegiancesWithIndex: { [key: string]: string } = { "Jon Snow": "Stark", "Daenerys Targaryen": "Targaryen" }; const getAllegianceWithIndex = (character: string) => allegiancesWithIndex[character]; ``` ## 2.4 How to handle variables that could be undefined ### Problem: ```typescript interface Character { name: string; title?: string; // The title is optional, hence it could be undefined } const character: Character = { name: "Arya Stark", // title is not provided }; const uppercaseTitle = (character: Character): string=> { return character.title.toUpperCase(); //Error: 'character.title' is possibly 'undefined'. } ``` ### 2.4.1. Optional chaining ```typescript interface Character { name: string; title?: string; } const arya: Character = { name: "Arya Stark", }; const uppercaseTitle = (character: Character): string | undefined =>{ //need to be the union string | undefined return character.title?.toUpperCase(); } ``` ### 2.4.2. Nullish Coalescing Operator (??) - for ES11 ```typescript interface Character { name: string; title?: string; } const arya: Character = { name: "Arya Stark", }; const uppercaseTitle = (character: Character): string =>{ return character.title?.toUpperCase() ?? "Title Unknown"; } ``` ### 2.4.3. Type Guards ```typescript interface Character { name: string; title?: string; } const arya: Character = { name: "Arya Stark", }; const uppercaseTitle = (character: Character): string =>{ if (character.title !== undefined) { return character.title.toUpperCase(); } else { return "Title Unknown"; } } ``` ### 2.4.4. Providing a Default Value ```typescript interface Character { name: string; title?: string; } const arya: Character = { name: "Arya Stark", }; //logical OR (||) operator const uppercaseTitle = (character: Character): string =>{ const title = character.title || "No Title"; return title.toUpperCase(); } //or default parameter const uppercaseTitle = (title: string = "Traveler"): string =>{ return title.toUpperCase(); } uppercaseTitle(arya.title) ``` ### 2.4.5. Type Assertion only use it when you're absolutely sure 'title' is not undefined ```typescript interface Character { name: string; title?: string; } const arya: Character = { name: "Arya Stark", title: "Princess" }; const uppercaseTitle = (character: Character): string =>{ return character.title!.toUpperCase(); ``` ## 2.5 How to fix error "'value' implicitly has an 'any' type" ```typescript const holdsIronThrone = (house) => { //Parameter 'house' implicitly has an 'any' type const ironThroneHolder = "Targaryen"; return house === ironThroneHolder; } console.log(holdsIronThrone("Stark")); ``` ### 2.5.1 Explicit Types or default value ```typescript //Explicit Types const holdsIronThrone = (house: string) => { const ironThroneHolder = "Targaryen"; return house === ironThroneHolder; } console.log(holdsIronThrone("Stark")); // No error, returns false //default value const holdsIronThrone = (house = "Unknown") => { const ironThroneHolder = "Targaryen"; return house === ironThroneHolder; } console.log(holdsIronThrone("Stark")); // No error, TypeScript infers house is a string from default value ``` ### 2.5.2 Interfaces ```typescript interface House { name: string; } const holdsIronThrone = (house: House) => { const ironThroneHolder: string = "Targaryen"; return house.name === ironThroneHolder; } console.log(holdsIronThrone({ name: "Stark" })); ``` ### 2.5.3 Interfaces ```typescript interface House { name: string; } //Define an interface for the function overloads interface holdsIronThrone{ (house: string): boolean; (house: House): boolean; // Single function implementation that handles all cases, assigned to a variable const holdsIronThrone : holdsIronThrone= (house: any): boolean =>{ const ironThroneHolder = "Targaryen"; const houseName = typeof house === 'string' ? house : house.name; return houseName === ironThroneHolder; } console.log(holdsIronThrone("Stark")); console.log(holdsIronThrone({ name: "Targaryen" })); ``` ## 2.6 How to fix error "Argument of type '..' is not assignable to parameter of type..." ```typescript interface House { name: string; sigil: string; } const declareAllegiance = (house: House) => { console.log(`${house.name} pledges loyalty to the realm.`); }; declareAllegiance({ name: "Stark", sigil: "Direwolf", motto: "Winter is Coming" }); ``` ### 2.6.1 Simply structuring the value ```typescript interface House { name: string; sigil: string; } const declareAllegiance = (house: House) => { console.log(`${house.name} pledges loyalty to the realm.`); }; declareAllegiance({ name: "Stark", sigil: "Direwolf"}); //remove motto to align with House interface ``` ### 2.6.2 Type Assertion Type assertions should be wielded with caution, for they tell TypeScript to trust the developer's knowledge over its own analysis. ```typescript interface House { name: string; sigil: string; } const declareAllegiance = (house: House) => { console.log(`${house.name} pledges loyalty to the realm.`); }; declareAllegiance({ name: "Stark", sigil: "Direwolf", motto: "Winter is Coming" } as House); ``` ## 2.7 How to fix error "Property Y does not exist on type X" ```typescript interface Hero { name: string; house: string; } const jonSnow: Hero = { name: "Jon Snow", house: "Stark", }; console.log(jonSnow.title); //Error: Property 'title' does not exist on type 'Hero'. ``` ### 2.7.1 Simply define new interface ```typescript interface HeroWithTitle { name: string; house: string; title: string; } const jonSnow: HeroWithTitle = { name: "Jon Snow", house: "Stark", title: "King in the North", }; console.log(jonSnow.title); ``` ### 2.7.2 Extending Interfaces ```typescript interface Hero { name: string; house: string; } interface NobleHero extends Hero { title: string; } const jonSnow: NobleHero = { name: "Jon Snow", house: "Stark", title: "King in the North", }; console.log(jonSnow.title); ``` ### 2.7.3 Optional Properties ```typescript interface Hero { name: string; house: string; title?: string; // An optional property } const jonSnow: Hero = { name: "Jon Snow", house: "Stark", title: "King in the North", }; console.log(jonSnow.title); ``` ### 2.7.4 Index Signatures ```typescript interface Hero { name: string; house: string; [key: string]: any; //allowing any property } const jonSnow: Hero = { name: "Jon Snow", house: "Stark", title: "King in the North", }; console.log(jonSnow.title); ``` ## 2.8 How to fix error "Object literal may only specify known properties, and X does not exist in type Y" ```typescript interface Alliance { houses: string[]; pact: string; } const newAlliance : Alliance = { houses: ["Stark", "Tully"], pact: "Mutual Defense", location: "Riverrun", // Error: Object literal may only specify known properties, and 'location' does not exist in type 'Alliance'. }; ``` ### 2.8.1 Extending Interfaces ```typescript interface Alliance { houses: string[]; pact: string; } interface DetailedAlliance extends Alliance { location: string; } // The new declaration is accepted const newAlliance: DetailedAlliance = { houses: ["Stark", "Tully"], pact: "Mutual Defense", location: "Riverrun", }; ``` ### 2.8.2 Optional Properties Sometimes, not all details are known or necessary. We can mark some properties as optional: ```typescript interface Alliance { houses: string[]; pact: string; location?: string; } // The new declaration is accepted const newAlliance: Alliance = { houses: ["Stark", "Tully"], pact: "Mutual Defense", location: "Riverrun", }; ``` ### 2.8.3 Index Signatures ```typescript interface Alliance { houses: string[]; pact: string; [key: string]: any; } // The declaration, no matter how detailed, is accepted const newAlliance : Alliance = { houses: ["Stark", "Tully"], pact: "Mutual Defense", location: "Riverrun", }; ``` ### 2.8.4 Type Assertion Use this tool with caution, you're telling TypeScript to trust our intention. ```typescript interface Alliance { houses: string[]; pact: string; } const newAlliance = { houses: ["Stark", "Tully"], pact: "Mutual Defense", location: "Riverrun", } as Alliance; ``` ### Other example ```typescript //error interface HouseWithKnights { name: "Stark" | "Lannister"; knights: number; } interface HouseWithCastle { name: "Tully" | "Greyjoy"; castle: string; } const declareAllegiance = (house: HouseWithKnights | HouseWithCastle) =>{} //Error: Object literal may only specify known properties, and 'knights' does not exist in type 'HouseWithCastle'. //Fix 1: Aligning Declaration with Expected Interface declareAllegiance({ name: "Tully", castle: "Riverrun" }); //or declareAllegiance({ name: "Stark", knights: 100 }); //Fix 2: Creating a More Inclusive Interface interface NobleHouse { name: string; knights?: number; castle?: string; } const declareAllegianceFlexibly = (house: NobleHouse) =>{ console.log(`House ${house.name} has declared their allegiance.`); } declareAllegianceFlexibly({ name: "Tully", knights: 100 }); // Now works without error //Fix 3: Using Type Assertions declareAllegiance({ name: "Tully", knights: 100 } as HouseWithKnights); // Fix 4: Extending Interfaces for Complex Scenarios interface BaseHouse { name: string; } interface HouseWithKnights extends BaseHouse { knights: number; } interface HouseWithCastle extends BaseHouse { castle: string; } type HouseWithKnightsAndCastle = HouseWithKnights & HouseWithCastle; function declareAllegianceComplex(house: HouseWithKnightsAndCastle) { console.log(`House ${house.name} with ${house.knights} knights guards ${house.castle}.`); } declareAllegianceComplex({ name: "Tully", knights: 100, castle: "Riverrun" }); ``` ## 2.9 Errors related to function signatures ## 2.9.1 Expected X Arguments, But Got Y ```typescript //error const sendRaven = (message: string, location: string) => { console.log(`Sending raven with message: ${message} to ${location}`); } sendRaven("White Walkers are coming"); // Error: Expected 2 arguments, but got 1. //1. Fulfill the arguments sendRaven("White Walkers are coming", "Winterfell"); //2. give default value for location const sendRaven = (message: string, location: string = "King's Landing") =>{ console.log(`Sending raven with message: ${message} to ${location}`); } sendRaven("All hail the King in the North!"); ``` ## 2.9.2 Expected X Arguments, But Got Y ```typescript type Advisor = (name: string, title: string) => string; const tyrionAdvisor: Advisor = (name: string) => `${name} is the Hand of the Queen`; // Error: Type provides no match for the signature. //1. fulfill the type of argurments const tyrionAdvisor: Advisor = (name: string, title: string) => `${name} is ${title}`; //2. make title optional type Advisor = (name: string, title?: string) => string; ``` ## 2.9.3 Cannot Invoke an Expression Whose Type Lacks a Call Signature ```typescript const spellbook = { decipher: "Ancient Texts Deciphering Spell", }; spellbook.decipher(); // Error: Cannot invoke an expression whose type lacks a call signature. //1. Define the spellbook with a callable function const spellbook = { decipher: (text: string) => `Deciphering: ${text}`, }; spellbook.decipher("Valyrian scrolls"); //2. If the spellbook must remain unchanged, create a separate function to invoke the spell. const decipherSpell = (text: string) => { console.log(`Deciphering: ${text}`); } decipherSpell(spellbook.decipher); ``` ## 2.9.4 A Function Whose Declared Type is Neither 'void' Nor 'any' Must Return a Value ```typescript const sendWarning = (signal: string): string =>{ // No return statement here } sendWarning("White Walkers!"); // Error: Function lacks return statement. //1. Ensure the function fulfills its duty by returning the appropriate warning signal. const sendWarning = (signal: string): string =>{ return `Warning: ${signal}`; } //2. If no specific warning needs to be returned, change the function's return type to void. const sendWarning = (signal: string): void => { console.log(`Warning: ${signal}`); } ```