Model Alternatives with Discriminated Union Types in TypeScript

InstructorMarius Schulz

Share this video with your friends

Send Tweet

TypeScript’s discriminated union types (aka tagged union types) allow you to model a finite set of alternative object shapes in the type system. The compiler helps you introduce fewer bugs by only exposing properties that are known to be safe to access at a given location. This lesson shows you how to define a generic Result<T> type with a success case and a failure case. It also illustrates how you could use discriminated unions to model various payment methods.

Thorben
~ 7 years ago

Hi Marius, this is a really great course!

I have an issue with discriminated union types.

I want to discriminate the props of my React component by visibleListType property. However if I am now passing the prop visibleListType={"ListView'} and e.g. keyExtractor={item => item.userId} to the AlwaysVisibleList component, I'd expect the TS complier to warn me about using the keyExtractor prop. This is because the keyExtractor is part of the FlatListProperties, but not the ListViewProperties.

Do you know what I am doing wrong here? I have strictNullChecks enabled.

interface OwnProps {
  customInsetWhenKeyboardIsHidden?: number;
  customInsetWhenKeyboardIsShown?: number,
}

type VisibleListType = 'ListView' | 'FlatList' | 'SectionList' ;

interface SectionListProps extends SectionListProperties<any>, OwnProps {
  visibleListType: VisibleListType;
}

interface FlatListProps extends FlatListProperties<any>, OwnProps {
  visibleListType: VisibleListType;
}

interface ListViewProps extends ListViewProperties, OwnProps {
  visibleListType: VisibleListType;
}

type Props =
  | SectionListProps
  | FlatListProps
  | ListViewProps

interface State {
}

class AlwaysVisibleList extends React.Component <Props, State> {
 \*...*\
}
Marius Schulzinstructor
~ 7 years ago

@Thorben: Some types are missing in your code example (e.g. SectionListProperties<T>), but from what I can see, you're not specifying a different discriminant for each of your props interfaces — you're defining a union type once ('ListView' | 'FlatList' | 'SectionList'), which is then shared by all props interfaces.

This is not how discriminants (aka tags) are meant to be used. Every props interface should define a property of the same name and a unique literal type, which is then used to differentiate between the possible cases. That doesn't work if you're using the same union type for every case.

Actum
~ 6 years ago

Hello Marius,

Thank you for your course.

I've found very tricky part.

when you write in the switch expression method.kind (like in your example) it works perfect.

but when I use destructurisation const { kind } = method; and put kind to the expression, the compiler complains to method.email: Property 'email' does not exist on type 'PaymentMethod'. Property 'email' does not exist on type 'Cash'.

Could you explain whats wrong?

Thank you

Marius Schulzinstructor
~ 6 years ago

@Actum: The TypeScript compiler only narrows the type of the method parameter if you're checking method.kind directly. It does not track that you've stored the value of method.kind within the kind local variable. You'll have to stick to method.kind to have the compiler narrow the type.

yu
~ 6 years ago
Argument of type '{ kind: string; email: string; }' is not assignable to parameter of type 'PaymentMethod'.
  Type '{ kind: string; email: string; }' is not assignable to type 'CreditCard'.
    Property 'cardNumber' is missing in type '{ kind: string; email: string; }'.
const myPayment: {
    kind: string;
    email: string;
}

ts complained above, Could you explain whats wrong? Thanks

Marius Schulzinstructor
~ 6 years ago

@yu: Could you post a small code example that produces the error you're talking about?

Ken Snyder
~ 5 years ago

Is there amy way to have a class implement a discriminated union? It seems to throw an error and i’ve read elsewhere of people having this issue. Would be a shame to lose this type specificity in a class where it is readily available in a POJO hash/dictionary.

Marius Schulzinstructor
~ 5 years ago

@Ken: No, that's not possible as far as I am aware. You'll have to stick with POJOs if you want to use discriminated unions.