DEV Community

Michael Sholty
Michael Sholty

Posted on

Using TypeScript to guard against specific React prop combinations

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:

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!

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!

// @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:

If you want to play around with this example, please check out the example in the TypeScript playground

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!

Top comments (3)

Collapse
 
dorshinar profile image
Dor Shinar

It's a nice approach but I find that it does not scale very well. If prefer something like:

type Shape = "Square" | "Circle";
type Props = { 
  name: string; 
  shape?: Shape; 
}
Collapse
 
svitekpavel profile image
Pavel Svitek • Edited

Of course, this is usual usage. But let's imagine a situation where you would like to restrict combination of parameters to a function which doesn't represent the same "type".

Collapse
 
msholtyfd profile image
Michael Sholty

Yep, this article was purely a thought exercise ✌🏻