React + TypeScript: Interdependent prop types

Published on in JavaScript, React and TypeScript

Last updated on

I.e. props that depend on other props. You can require and forbid certain prop combinations by utilizing TypeScript's union and intersection types.

Table of contents

Example problem

Let's say we want to create a Shape React component with the following requirements:

  • It takes a color prop.
  • It takes a type prop with the value of 'circle', 'square' or 'rectangle'.
  • Depending on the type prop's value, it takes the following props as well:
    • radius when the type is 'circle'.
    • width when the type is 'square'.
    • width and height when the type is 'rectangle'.

A naive type for the props object would look like this:

type ShapeProps = {
  color: string
  type: 'circle' | 'square' | 'rectangle'
  radius: number
  width: number
  height: number

function Shape(props: ShapeProps) {
  return /* ... */

It's naive because we could specify all of those props:

<Shape color="red" type="circle" radius={10} width={20} height={30} />

That makes no sense because a circle shape doesn't have a width or a height; it only has a radius.

The other shapes have similar problems.

Union type to the rescue

We instead want to create a union type:

type ShapeProps =
  | { color: string; type: 'circle'; radius: number }
  | { color: string; type: 'square'; width: number }
  | { color: string; type: 'rectangle'; width: number; height: number }

Now TypeScript won't let us specify invalid props.

For example, when the type is 'circle':

  • We can (and must) specify only the color and radius props.
  • We can't specify the width or height props; doing so would produce an error.

Simplify with an intersection type

Notice how we are repeating the color prop in all three types.

We can reduce duplication by using an intersection type:

type ShapeProps = { color: string } & (
  | { type: 'circle'; radius: number }
  | { type: 'square'; width: number }
  | { type: 'rectangle'; width: number; height: number }

More tricks

I learned this trick from Bruno Antunes's YouTube video React.js TypeScript Conditional Props – Props that depend on other Props (26:13). Check it out, it has more advanced examples as well.