Search code examples
javascripttypescriptintellisenseoptional-chaining

VSCode IntelliSense displaying an error on Optional Chaining in TypeScript


In VSCode, IntelliSense is saying a property does not exist on an object in a TypeScript file even when I specify the optional chaining syntax to short-circuit the error. The code compiles correctly to JavaScript es2020.

Here is the typescript code

"use strict"
const animals = {
    dog: {
        name: 'Rover'
    }
};
const catName = animals.cat?.name;          ** *<<<<< the squiggly line appears under cat***
console.log(catName);

The actual TypeScript error is

    error TS2339: Property 'cat' does not exist on type '{ dog: { name: string; }; }'.

I understand that the VSCode ships with its own version of Typescript so I added the following to my workspace settings.

{
    "typescript.tsdk": "node_modules/typescript/lib",
}

When I hover over the typescript version number on the VSCode status bar it shows the following which is a valid path.

D:\projects\test-ts\node_modules\typescript\lib\tsserver.js

I also tried disabling all VSCode Extensions however this did not change the result.

My VSCode version is 1.88.1 My TypeScript version is 5.45

FYI, I'm using Windows 10.

All my research and review of other posts lead back to the typescript’s "typescript.tsdk" setting but this has not solved the problem.

Below is my tsconfig.json file.

{
"compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "rootDir": "./src",
    "outDir": "./js",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
},
  "include": [
    "src"
  ]
}

How can I eliminate the error that is being displayed in VSCode?


Solution

  • There is nothing wrong with your TypeScript installation, in fact it's doing exactly what it should be doing. Let's break down the issue here:

    const animals = {
        dog: {
            name: 'Rover'
        }
    };
    

    You have a constant definition that has an inferred type, which could explicitly be written like so

    type Animals = {
      dog: {
        name: string
      }
    }
    

    If you want this to be more flexible, then you will need to either extend the type manually or loosen the type altogether.

    Solution #1

    Let's say you want all sorts of animals and it doesn't matter what they are called (i.e. cat, dog, bird...) you could do something like this:

    type Animals = {
      [key: string]: {
        name: string
      }
    }
    
    const animals: Animals = {
        dog: {
            name: 'Rover'
        }
    };
    
    const catName = animals.cat?.name; 
    console.log(catName);
    

    Here we have a type Animals which can contain any type of animal, which is just an object with the property name.

    Solution #2

    If you want to explicitly only allow animals which are either cat or dog then you could do the following

    type Pet = {
      name: string
    }
    
    type Animals = {
      cat?: Pet,
      dog?: Pet,
    }
    
    const animals: Animals = {
        dog: {
            name: 'Rover'
        }
    };
    
    const catName = animals.cat?.name; 
    console.log(catName);
    

    NOTE: in this case it's possible for both cat and dog to both be defined at the same time, both undefined at the same time, or a combination of defined and undefined

    Solution #3

    If you want these objects to behave like normal JS objects which can contain basically any key / value, you could also use a looser type like Record

    const animals: Record<string, any> = {
        dog: {
            name: 'Rover'
        }
    };
    
    const catName = animals.cat?.name; 
    console.log(catName);
    

    I would recommend against using any when possible as it somewhat defeats the purpose of TS, but you could also replace that any with whatever type you wanted, for example

    const animals: Record<string, { name: string }> = {
        dog: {
            name: 'Rover'
        }
    };
    

    or even better

    const animals: Partial<Record<'cat' | 'dog', { name: string }>> = {
        dog: {
            name: 'Rover'
        }
    };
    

    How to use TypeScript Record<key, value>

    Additional Comments

    TypeScript is showing an error here because from it's perspective you are trying to access .cat on an object which does not contain any such property.

    This is actually why TS is incredibly useful, especially in large teams, because it helps catch semantic issues prior to run-time.

    This is also why TS can be annoying, especially when refactoring old JS codebases which can be quite large. Sometimes you just wanna get the code working and not fiddle with the types, in which case you can do the following

    const animals: any = {
        dog: {
            name: 'Rover'
        }
    };
    

    By using the type any you are basically specifying the type can literally be anything, but this somewhat defeats the purpose of TS in the first place 😅

    TypeScript Do's & Don'ts of Any