React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`
Tags: react, typescript, cleancode, tutorial
Published: 2022-10-19 18:11:46


This trick is for those occasions when you use a class component, and don't want TypeScript to complain about optional props being possibly `undefined`, despite definitely being included in `defaultProps`.

In a word, how to fix this:


![TypeScript might not know the that defaultProps were included](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c244au4ce2x4a30smri.png)<figcaption>Unfortunately, **TypeScript doesn't know that `defaultProps` get added to `this.props`**. And so we get `Object is possibly 'undefined'.` in places where we shouldn't. *Quelle horreur!*</figcaption>

---

Contents:
- [TLDR](#tldr)
- [Explained](#explained)
- [Drawbacks](#drawbacks)
- [Conclusion](#conclusion)
- [Further reading](#further-reading)

---

## TLDR

There are two ways to solve this. Both should be simple & easy to understand.

---

### Case 1: Simple

If your `Props` interface is not used anywhere else, you may want to just remove the optional `?:` from the type:

```ts
interface RatingProps {
  /**
   * The rating out of 10.
   *
   * @default 10
   */
  rating: number; // <--- remove the `?`
}
```

As long as the prop is included in the component's `defaultProps`, React's typing should be smart enough to not force you to specify the prop when using the component:

```ts
return <Rating /> // `rating` is required, but we don't need to specify it because it's included in `defaultProps`
```

**This is the recommended way to solve this problem**, if you're writing the code from scratch. This solves the "possibly `undefined`" error within the component without creating errors elsewhere, **provided that no other types depend on the `Props` interface**.

But if `interface Props {}` is already interdependent with other types, and you don't want to refactor them, then you might benefit from the fancier solution below:

---

### Case 2: Slightly more complicated

It may be you can't make the `props` required, or feel it would be unnecessarily complicated to do so (for example, if many other types depend on the type of your `props`). In that case, you can still fix this error.

All you need is this one-line trick:

```ts
import React, { Component } from "react";

interface RatingProps {
  /**
   * The rating out of 10.
   *
   * @default 10
   */
  rating?: number;
}

class Rating extends Component<RatingProps> {
// ---------
  declare readonly props: RatingProps &
    Required<Pick<RatingProps, keyof typeof Rating.defaultProps>>; // <--- Add me!
// ---------
  
  constructor(props) {
    super(props);
  }

  static defaultProps = {
    rating: 10,
  };

  render() {
    const { rating } = this.props;

    if (rating < 5) return <p>Fail</p>;
    return <p>{rating}</p>;
  }
}

export default Rating;
```

(Well, two if you include formatting... 😉)

This method should be fool-proof: `this.props` should now be perfectly typed within the component, and no other side-effects should be produced in any types anywhere else.


![It is very easy to make TypeScript be able to understand that defaultProps were included in this.props](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ltedqx3txil1dy8gyr9s.jpg)<figcaption>Declare your own `props`, and *voilà*! Problem solved!</figcaption>

---

## Explained

React's [type definitions file](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L490) (`index.d.ts`) defines the props as:
```ts
readonly props: Readonly<P>;
```

The reason method #1 works, is because of [this definition](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L3112) in the same file:
```ts
type ReactManagedAttributes<C, P> = C extends { propTypes: infer T; defaultProps: infer D; }
    ? Defaultize<MergePropTypes<P, PropTypes.InferProps<T>>, D>
    : C extends { propTypes: infer T; }
        ? MergePropTypes<P, PropTypes.InferProps<T>>
        : C extends { defaultProps: infer D; }
            ? Defaultize<P, D>
            : P;
```
in which React infers the leftover required `props` based on the supplied `defaultProps`. 

Method #2 works because of the following: by declaring our own `props` at the top of the component, we simply overwrite React's definition of `props` *inside the component definition itself* to not only use the `P` (i.e., the type of the component's passed in `Props`) but also its `defaultProps`. (In TypeScript, this is known as a "[type-only field declaration](https://www.typescriptlang.org/docs/handbook/2/classes.html#type-only-field-declarations)".)

Pretty neat, huh?

---

Another pro-tip: I've actually written a reusable helper type — `WithDefaultProps` — to make this trick easier:

```ts
/**
 * Like `Required`, but you can choose which keys to make required. (`Required` makes all keys required).
 *
 * ---
 *
 * Example:
 * `typescript
 * type Obj = { a?: 1; b?: 2; c?: 3 };
 * type PartiallyRequiredObj = Imposed<Obj, 'a' | 'b'>; // equivalent to `{ a: 1; b: 2; c?: 3 }`;
 * `
 */
export type Imposed<T, K extends keyof T> = T & Required<Pick<T, K>>;

/**
 * A helper for React class components with `static defaultProps`.
 *
 * To use, simply add `declare readonly props:` at the top of the class:
 * `ts
 * class MyComponent extends Component<Props>{
 *  declare readonly props: WithDefaultProps<Props, typeof MyComponent.defaultProps>;
 *
 *  // ...
 * }
 * `
 */
export type WithDefaultProps<PassedInProps, DefaultProps extends object> =
  Readonly<Imposed<PassedInProps, keyof DefaultProps>>;

```

---

## Drawbacks

As far as I can tell, there are no drawbacks to either of these methods. (Keep in mind that method #1 is just using React's `defaultProps` in a way that's working as intended).

For the custom `props` declaration method, try to keep in mind that you are forcefully overwriting the `props`. You must beware of typos which might ruin your type, especially when copy-pasting. Forgetting about this fact might lead to surprises if you don't keep in mind where the types come from.

Also, note that you have to *know* that `defaultProps` will definitely be passed in, because TypeScript won't know this. And a React novice might not know that either, so it might be a good reason to add a JSDoc comment over your custom `props` declaration. (Not to mention that the seniors will probably wonder what the heck you're doing 😄)

Otherwise, use it to your heart's content! 🙂

---

## Conclusion

In this article, we saw two quick & easy ways to make sure that your `this.props` always remains type-safe.

If you're one of the few people still using class components, then this tip may have been helpful for you 😄

---

## Further reading:
- React's [type definitions file](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts)
- TypeScript's ["type-only" field declarations](https://www.typescriptlang.org/docs/handbook/2/classes.html#type-only-field-declarations)