vbvx
vbvx

Reputation: 619

typescript index signature that index optional properties

I would like to declare something like this in typescript 3.5.3

interface User {firstName: string}
interface Contacts {[index: string]: User | undefined}

Basically I would like typescript to warn me about the fact that the property I am trying to access is potentially undefined. Currently, that's not the case when I write this:

const contacts: Contact = {};
const randomId = '1240astat0252';
const user = contacts[randomId]; //typescript doesn't warn me about this being undefined

edit: in the specific project I was working on I forgot to set strict: true in the tsconfig which would not raise an error for user.firstName. In the original state of the question, the answer is correct.

Upvotes: 3

Views: 7763

Answers (1)

Tim Perry
Tim Perry

Reputation: 13286

In general, TypeScript will only warn you when you're doing something that's potentially invalid or very likely to be a mistake.

In this case, you're not. You're reading a field that may be undefined, but that won't immediately cause an error, and it's a dictionary-style object which suggests that's normal behaviour.

If you do do something that's definitely dangerous, such as use that user variable and assume that it is defined, then you'll get an error. For example:

interface User {firstName: string}
interface Contact {[index: string]: User | undefined}

const contacts: Contact = {};
const randomId = '1240astat0252';
const user = contacts[randomId];
console.log(user.firstName); // Error: Object is possibly 'undefined'

For most cases, that's enough. This ensures that any code that really uses retrieved properties must first ensure their value is defined (e.g. if (user) { ... }).

If you want an error on any unknown property accesses, you need to remove the index signature. To then actually access fields with no index signature, you'll need to show TypeScript that the field definitely exists. There's a few options, but as an example you could use a custom type guard:

interface User {firstName: string}

type HasContact<N extends string> = { [name in N]: User };
function hasContact<N extends string>(x: {}, name: N): x is HasContact<N> {
    return name in x;
}

const contacts = {};
const randomId = '1240astat0252';

const user = contacts[randomId]; // Error

if (hasContact(contacts, randomId)) {
    const user = contacts[randomId]; // Inside the 'if' this is all good
    contacts['differentId']; // Still an error
}

Upvotes: 4

Related Questions