Notes on TypeScript: Inferring React PropTypes
Tags: typescript, react, types
Published: 2019-03-10 15:25:06

---
title: Notes on TypeScript: Inferring React PropTypes
published: true
description: Inferring React PropTypes
tags: typescript, react, types
series: Notes on TypeScript
---

### Introduction

These notes should help in better understanding `TypeScript` and might be helpful when needing to lookup up how to leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

### PropTypes and Inference

More often than not we might be working on an existing __React__ application, where a team has decided to introduce __TypeScript__. This would also mostly mean that if Components props have to be defined at some point, either defining all at once or gradually. In some cases there also might be existing _prop-type_ definitions. Instead of removing the existing prop-type definitions, we might be able to build a bridge between these prop-types and TypeScript.

In this part of the "Notes on TypeScript" we will learn how we can leverage TypeScript to infer these component prop-types.

Before we begin, it's important to note that the `PropTypes` type package offers `PropTypes.InferProps`, which enables to infer the types for an existing prop-type definition like so:

```js
const userPropTypes = {
  id: PropTypes.number.isRequired,
  name: PropTypes.string.isRequired,
  active: PropTypes.bool
};

types UserProps = PropTypes.InferProps<typeof userPropTypes>;
```

But let's take a step back for a minute. What does calling `typeof` on a PropType definition return?

```js
type TypeOfUserProps = typeof userPropTypes;

/*
  type TypeOfUserProps = {
    id: PropTypes.Validator<number>;
    name: PropTypes.Validator<string>;
    active: PropTypes.Requireable<boolean>;
  }
*/
```

If we take a closer look at `userPropTypes`, we can see that `id` and `name` are required and that the `active` flag is optional. When inferring these prop-type definitions, we can see that the returned type definition wraps the defined types into either a `Validator` or `Requireable`, depending on the fact, that the type has been defined as required or not.

So internally `PropTypes.InferProps` differentiates between required and optional types and then creates an intersection between these two groups. We won't go too deep into the implementation details, but there is a need to find out if a prop-type is required or not. An internal `IsOptional` type checks if a type is `null | undefined` and then determines if the type is optional.

Next, let's build a small example, to verify if we can transform any prop-type definitions to actual TypeScript types. We have a `User` component with existing prop-type and default props definitions.

```js
const userPropTypes = {
  id: PropTypes.number.isRequired,
  name: PropTypes.string.isRequired,
  active: PropTypes.bool
};

const userDefaultProps = {
  name: "Test"
};

const User = (props /*: PropTypes? */) => {
  return (
    <div>
      id: {props.id}
      name: {props.name}
      status: {props.active ? "active" : "inactive"}
    </div>
  );
};

User.defaultProps = userDefaultProps;
```

How can we infer these `userPropTypes` and provide the missing types for the `User` component?

Our first approach would be to go back to the very first example in this write-up:

```js
const userPropTypes = {
  id: PropTypes.number.isRequired,
  name: PropTypes.string.isRequired,
  active: PropTypes.bool
};

types UserProps = PropTypes.InferProps<typeof userPropTypes>;
```

If we verify the example, we can see that this is already working as expected.

```js
const User = (props: UserProps) => {
  // ...
}

<User id={1} /> // Works!
<User id="1!" /> // Error! Type 'string' is not assignable to type 'number'
<User /> // Error! Property 'id' is missing
```

But we're not considering any default props, although this should not be the case, they might have a different type. This means we need to extend the existing mechanism to include the inference of default props.
There is not very much we need to do:

```js
type InferPropTypes<
  PropTypes,
  DefaultProps = {},
  Props = PropTypes.InferProps<PropTypes>
> = {
  [Key in keyof Props]: Key extends keyof DefaultProps
    ? Props[Key] | DefaultProps[Key]
    : Props[Key]
};
```

If we take a closer look at the above `InferPropTypes` type, we can notice that we accept the prop-type and default prop types and then infer the provided prop-types. Next, we map over these inferred prop types and check if a key is also defined in the default props. The `key extends keyof DefaultProps` part helps us to find out if a key actually exists in the default props. If this is the case we return a union of the prop and default prop value type else we only return the prop value type.

Finally we can use our newly defined `InferPropTypes` as shown in the next example.

```js
type UserProps = InferPropTypes<typeof userPropTypes, typeof userDefaultProps>;
```

Running our `User` component again, shows that everything works as expected.

```js
const User = (props: UserProps) => {
  // ...
}

<User id={1} /> // Works!
<User id="1!" /> // Error! Type 'string' is not assignable to type 'number'
<User /> // Error! Property 'id' is missing
```


We should have a basic understanding of how we can infer existing prop-type definition in a React application when working with TypeScript.

If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)