Avatar

Ciao, I'm Julia.

Override Part of an Existing Type in Typescript

#react #react-native #typescript

3 min read

I've recently been on a mission to eliminate all Typescript errors in our code base at work, and have been steadily chipping away at them systematically, during any downtime I get. I used to dislike Typescript when I first came across it, but really love it now and get an immense sense of satisfaction watching the red squigglies disappear as I work through each Typescript issue, bit by bit. 🤓

It was whilst doing this last week that I came across a situation that was new to me. What I was faced with was this...

I have a component that takes in a prop item which has an interface IItem defined as:

interface IItem extends PickerItemProps {
  iconSource?: ImageSourcePropType;
}

// ...and separately in another file, the React Native library provides this interface
export interface PickerItemProps {
  testID?: string;
  color?: ColorValue;
  label: string;
  value?: any;
}

If you're not familiar with Typescript, extends effectively merges the PickerItemProps interface into IItem so that item can take this form, for example:

const item: IItem = { iconSource: 'imagesourcelocation', label: 'An item' }

// How it's used
;<ParentComponent item={item} />

This issue I was faced with involved needing to extend the type label. Instead of it always being a plain string, I needed to allow label to also take the form of a component (the specific reason behind this has to do with how we're formatting certain bits of text in our app). Ideally then, label needs to either be of type string OR JSX.Element.

Because label actually belongs to and is defined in an external package, I didn't want to just change PickerItemProps directly (since these might unknowingly be overwritten on upgrades etc.). I therefore naively tried to do this, but quickly ran into Typescript complaining loudly.

interface IItem extends PickerItemProps {
  iconSource?: ImageSourcePropType;
  label: string | JSX.Element; // Typescript complains that label already exists
}

// No changes to this
export interface PickerItemProps {
  testID?: string;
  color?: ColorValue;
  label: string;
  value?: any;
}

So what to do? The answer lies in using one of Typescript's utility types Omit. From the Typescript documentation, Omit<Type, Keys>...

Constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).

What I could do in my case therefore, was to extends PickerItemProps without label, before then defining label as part of IItem like so:

interface IItem extends Omit<PickerItemProps, 'label'> {
  iconSource?: ImageSourcePropType;
  label: string | JSX.Element;
}

// Original definition remains untouched
export interface PickerItemProps {
  testID?: string;
  color?: ColorValue;
  label: string;
  value?: any;
}

© 2016-2024 Julia Tan · Powered by Next JS.