DEV Community

loading...

Using Typescript Unions With Never and Extends

Bionic Julia
Formerly a banker and tech startup founder. Now on my 3rd career as a full stack software engineer (with a front end focus). Posts on tech and things learnt on my programming journey. 👩🏻‍💻
Originally published at bionicjulia.com on ・2 min read

I've reached a point where I'm now really enjoying working with Typescript. I remember one of the senior engineers in my team telling me when I first started that I would find myself being a Typescript convert one day. I didn't really believe it then, as TS seemed to be so much more of a hindrance than help in the early days (it wouldn't even allow me to run my code to debug it!)...but I'm happy to say he was right. ✌🏻

I've reached the tipping point, where it now feels weird to NOT have Typescript switched on for a Javascript repo. There are, for sure, still days when I'm trying to get a specific scenario typed properly and I'm finding it difficult to even know what to Google for... but it gets easier over time as you start to build a bank of knowledge and realise that the same patterns keep cropping up again and again.

One pattern that I keep coming across is this one - let's say I have a Media component that I need to pass certain props into, for example:

{
  id: number;
  group: string;
  bookTitle: string;
  ebook: boolean;
  tvShowTitle: string;
  platform: string;
  songTitle: string;
  artist: string;
}
Enter fullscreen mode Exit fullscreen mode

There are certain props that always needs to be present for the Media component to render, with the rest being conditional on whether certain other props are present:

// Always required
{
  id: number;
  group: string;
}

// Also need to have either Option 1 or 2 present.

// Option 1: Both required, or neither present
{
  bookTitle: string;
  ebook: boolean;
}

// Option 2: Both required, or neither present
{
  tvShowTitle: string;
  platform: string;
}
Enter fullscreen mode Exit fullscreen mode

How do we ask Typescript to check that if say bookTitle is passed as a prop, ebook becomes a required prop, whereas tvShowTitle and platform should NOT be present? Additionally, we want to let Typescript know that id and group should ALWAYS be required.

The answer is through a combination of union types and extends.

interface MediaBase {
  id: string;
  group: string;
}

interface BookProps extends MediaBase {
  bookTitle: string;
  ebook: boolean;
  tvShowTitle: never;
  platform: never;
}

interface TvShowProps extends MediaBase {
  bookTitle: never;
  ebook: never;
  tvShowTitle: string;
  platform: string;
}

export type MediaProps = BookProps | TvShowProps;
Enter fullscreen mode Exit fullscreen mode

MediaProps here is the final type of the Media component.

  • This can either take the form of BookProps or TvShowProps (union type).
  • We are explicit in our BookProps and TvShowProps using never to tell Typescript that these props should not be passed in if certain other props are present.
  • By using extends MediaBase, both BookProps and TvShowProps also has id and group as required props.

Is there a better way I can do this? Let me know on Twitter or Instagram @bionicjulia!

Discussion (0)