Try   HackMD

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.

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.

let jonSnowAge: number = 25; // Corrected

2.2.2. Object Shape Mismatch

The assigned object doesn't match the expected shape

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:

//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

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.

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.

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.

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.

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.

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.

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.

let jonSnowWeapon: Weapon = 'Sword'; 

2.2.7. Incorrect Generic Types

Using a generic type that does not conform to the constraints or expected structure.

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:
    ​​​​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:
    ​​​​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:
    ​​​​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

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

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

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

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

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:

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:

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

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

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

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

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

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"

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

//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

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

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"

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

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.

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"

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

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

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

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

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"

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

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:

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

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.

interface Alliance {
  houses: string[];
  pact: string;
}

const newAlliance = {
  houses: ["Stark", "Tully"],
  pact: "Mutual Defense",
  location: "Riverrun",
} as Alliance;

Other example

//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.1 Expected X Arguments, But Got Y

//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

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

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

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}`);
}