React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`

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:

Unfortunately, TypeS…


This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Deckstar

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 includedUnfortunately, 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!

Contents:

  • TLDR
  • Explained
  • Drawbacks
  • Conclusion
  • 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:

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:

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:

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.propsDeclare your own props, and voilà! Problem solved!

Explained

React's type definitions file (index.d.ts) defines the props as:

readonly props: Readonly<P>;

The reason method #1 works, is because of this definition in the same file:

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".)

Pretty neat, huh?

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

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


This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Deckstar


Print Share Comment Cite Upload Translate Updates
APA

Deckstar | Sciencx (2022-10-19T18:11:46+00:00) React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`. Retrieved from https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/

MLA
" » React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`." Deckstar | Sciencx - Wednesday October 19, 2022, https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/
HARVARD
Deckstar | Sciencx Wednesday October 19, 2022 » React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`., viewed ,<https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/>
VANCOUVER
Deckstar | Sciencx - » React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/
CHICAGO
" » React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`." Deckstar | Sciencx - Accessed . https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/
IEEE
" » React Pro Tip #2 — How to Type `this.props` to Include `defaultProps`." Deckstar | Sciencx [Online]. Available: https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/. [Accessed: ]
rf:citation
» React Pro Tip #2 — How to Type `this.props` to Include `defaultProps` | Deckstar | Sciencx | https://www.scien.cx/2022/10/19/react-pro-tip-2-how-to-type-this-props-to-include-defaultprops/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.