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:
Contents:
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.
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:
- React's type definitions file
- TypeScript's "type-only" field declarations
Top comments (1)
Dear sir,
I'm helping a friend of mine to run the program you posted on gitHub on the Multifractal model of asset returns and because he is trying to run the simulation on different markets than the ones you used we're encountering some issues we would require your help to overcome.
He is writing his thesis on the mmar Mandelbrot model and your program is of vital importance for the results he needs to obtain.
We are looking forward to find a way we can get in touch with you, thank you in advance.