Is narrowing down a union type with optional chaining possible?

Viewed 441

I have a discriminating union, simplified into this example.

type FoobarEvent = FooEvent | BarEvent;

interface FooEvent {
    type: 'foo';
    foo: {
        name: string;
    }
}

interface BarEvent {
    type: 'bar';
    bar: {
        id: string;
    }
}

Of course I can check the discriminant and get the union type narrowed down properly.

// let's assume this exists
declare const foobarEvent: FoobarEvent;

let fooName: string | undefined;

if (foobarEvent.type === 'foo') {
    fooName = foobarEvent.foo.name;
}

However I want to avoid this if-block, so I tried to use optional chaining to narrow down the type, but that doesn't work because one of the unions doesn't have the field I'm trying to access.

const fooNameByChaining = foobarEvent.foo?.name;

Property 'foo' does not exist on type 'FoobarEvent'.
 Property 'foo' does not exist on type 'BarEvent'.

So apparently the narrowing would need to happen before the optional chaining. Is this a limitation of TypeScript's type system or could this be added to Typescript as a feature? Or is there another way around this that I'm not aware of?

Playground Link

1 Answers

If TypeScript would allow you to attach .foo?.name on any type, it would defeat type checking whenever we use ?. operator.

For example, is my foobarEvent of a type, where foo is optional property:

type FoobarEvent = {
    id: number;
    bar: string;
    foo?: {
        name: string;
    }
}

in which case my intention is correct:

const name = foobarEvent.foo?.name;

or - did I create a typo by adding .foo?.name to a wrong type?

const name = localStorage.foo?.name;
Related