Using TypeScript to guard against specific React prop combinations
Tags: react, typescript, javascript, testing
Published: 2020-04-10 13:43:49

_Preface: I know you may see the example code here and want to refactor it, but that would defeat the purpose of the exercise. Suspend disbelief!_

Imagine you have a React component like so:

```tsx
type Props = {
  name: string;
  isCircleBadge?: true;
  isSquareBadge?: false;
};

export function NameBadge(props: Props) {
  if (props.isCircleBadge && props.isSquareBadge) {
    console.warn('A NameBadge cannot both be circle and square. Please only pass in one of these props');
  }
  if (props.isCircleBadge) {
    return <circle>{props.name}</circle>;
  }

  return <div>{props.name}</circle>
}
```

In this example, we're writing JavaScript to warn a developer consuming this component to not misuse it. In regular JavaScript, this seems like a reasonable solution since we don't have the power of types static analysis. However, in TypeScript we can guard against this with our tooling so the developer gets immediate feedback in their editor when they misuse it, instead of hoping they see it in the console!

```tsx
import React from 'react';

type Props = {
  name: string;
} & IndicatorStates;

type IndicatorStates =
  | {
      isCircleBadge?: true;
      isSquareBadge?: false;
    }
  | {
      isCircleBadge?: false;
      isSquareBadge?: true;
    }
  | {
      isCircleBadge?: false;
      isSquareBadge?: false;
  };

// The point here is that you should not pass in both isCircleBadge
// and isSquareBadge as true, since a name badge can only be one shape
export function NameBadge(props: Props) {
  if (props.isCircleBadge) {
    return <circle>{props.name}</circle>;
  }

  return <div>{props.name}</circle>
}
```

Here I am defining explicit states that are acceptable for `isCircleBadge` and `isSquareBadge` boolean props. Now when you try to misuse the component, you'll get a TypeScript error instead!
```tsx
// @ts-expect-error NameBadge must have a shape defined
const test1 = <NameBadge name="Michael" />

// This is fine
const test2 = <NameBadge name="Michael" isCircleBadge={true} />

// So is this
const test3 = <NameBadge name="Michael" isSquareBadge={true} />

// This doesn't work because NameBadge cannot have both isSquareBadge and isCircleBadge true
const test4 = <NameBadge name="Michael" isSquareBadge={true} isCircleBadge={true} />
```

Here's the error you'd get:
![](https://cdn.zappy.app/ee39e2c1c62857587b3aef3c1e821048.png)

If you want to play around with this example, please [check out the example in the TypeScript playground](https://www.typescriptlang.org/play/?jsx=2&ssl=1&ssc=1&pln=41&pc=85#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wG4AocmATzCTgAUcwBnOAXjgG9y44A7FCCQAuOCxhRg-AOYUAvnABkcAJL8AJsDQoY0AMoxdSFhSq166rTr1RDxtu15wAPt2d8+wFgGFgUNAAbJAAhFA0ZJAB+MUkAVyQKT09vfQBHOJQiMIjosUwUQJZEjzh5ZzceZK9ffyDQ8MiYuAKikuq4VIyshtzm+PbPcr5K0pq-AOCcpvzC4qTqrszsxryWucH5MwB6bbgAFQALekhpeGOiTrYYQ904agg48UPHwI0BCHgwFBY2aTgAEafQ5XCb1aZIci7OAoTRXdLLXqRGHXKAJAA04mkaHoKAEQnoANWcB0-DgEH4gWogPoFPoLFudHISAAHpBYC04vwMMAKXAAHIEiEACjAzBYYiYEFYAEp3F5MHBReKAHTeMFTVZyqqeIgwOJQMkAHjQdWCcG2AD4FuVnHqDcatAA3C2W8i26EAARgLAAtKy6Bh-VAcFABULiSA4hI4Lcnbjnig6HANEhMNIkBpyGgKTGYCYYABGDhwI2C4QQ-HCdgAIgAstpbkhAjXXZRoUdvFcWhns7n4PmJAAmEtliO5KtIWsNtBNlugs1IqdcAaKK3tvb6CDdm7ePv8PMFgDMo-LS8n08bKGbraWPQh7BXaKQa7dUL2nbYGggJn4+HgADu0AANY0jo0b0Gelakvwnyxig8aAsC8LdCsE6wu86qLpWAz7oeEgACynuOyKCNW9ZXjeKGIg+T4JIoWGTEuj6rq6QA)

I kinda wish we could create our own TypeScript error messages for specific cases like this, but this will do for now. Hope you enjoyed my article!