diff --git a/docs/basic/getting-started/default-props.md b/docs/basic/getting-started/default-props.md index 7bb7c944..5a2ffdcd 100644 --- a/docs/basic/getting-started/default-props.md +++ b/docs/basic/getting-started/default-props.md @@ -3,25 +3,57 @@ id: default_props title: Typing defaultProps --- -## Typing defaultProps +## You May Not Need `defaultProps` -For TypeScript 3.0+, type inference [should work](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html), although [some edge cases are still problematic](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61). Just type your props like normal, except don't use `React.FC`. +As per [this tweet](https://twitter.com/dan_abramov/status/1133878326358171650), defaultProps will eventually be deprecated. You can check the discussions here: + +- https://twitter.com/hswolff/status/1133759319571345408 + +The consensus is to use object default values. + +Function Components: + +```tsx +type GreetProps = { age?: number }; + +const Greet = ({ age = 21 }: GreetProps) => // etc +``` + +Class Components: + +```tsx +type GreetProps = { + age?: number; +}; + +class Greet extends React.Component { + const { age = 21 } = this.props + /*...*/ +} + +let el = ; +``` + +## Typing `defaultProps` + +Type inference improved greatly for `defaultProps` in [TypeScript 3.0+](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html), although [some edge cases are still problematic](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61). + +**Function Components** ```tsx -// //////////////// -// function components -// //////////////// type GreetProps = { age: number } & typeof defaultProps; const defaultProps = { age: 21, }; const Greet = (props: GreetProps) => { - /*...*/ + // etc }; Greet.defaultProps = defaultProps; ``` +_[See this in TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAOKVYwAKxY6ALxwA3igDmWAFxwAdgFcQAIyxQ4AXzgAyOM1YQCcACZYCyeQBte-VPVwRZqeCbOXrEAXGEi6cCdLgAJgBGABo6dXo6e0d4TixuLzgACjAbGXjuPg9UAEovAD5RXzhKGHkoWTgAHiNgADcCkTScgDpkSTgAeiQFZVVELvVqrrrGiPpMmFaXcytsz2FZtwXbOiA)_ + For **Class components**, there are [a couple ways to do it](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/pull/103#issuecomment-481061483)(including using the `Pick` utility type) but the recommendation is to "reverse" the props definition: ```tsx @@ -41,42 +73,88 @@ let el = ; ```
- An alternative approach + + + `JSX.LibraryManagedAttributes` nuance for library authors + + -As per [this tweet](https://twitter.com/dan_abramov/status/1133878326358171650), defaultProps will eventually be deprecated. You can check the discussions here: +The above implementations work fine for App creators, but sometimes you want to be able to export `GreetProps` so that others can consume it. The problem here is that the way `GreetProps` is defined, `age` is a required prop when it isn't because of `defaultProps`. -- https://twitter.com/hswolff/status/1133759319571345408 +The insight to have here is that [`GreetProps` is the _internal_ contract for your component, not the _external_, consumer facing contract](https://github.com/typescript-cheatsheets/react/issues/66#issuecomment-453878710). You could create a separate type specifically for export, or you could make use of the `JSX.LibraryManagedAttributes` utility: -The consensus is to use object default values. +```tsx +// internal contract, should not be exported out +type GreetProps = { + age?: number; +}; + +class Greet extends Component { + static defaultProps = { age: 21 }; +} + +// external contract +export type ApparentGreetProps = JSX.LibraryManagedAttributes< + typeof Greet, + GreetProps +>; +``` + +`` +This will work properly, although hovering over `ApparentGreetProps` may be a little intimidating. You can reduce this boilerplate with the `ComponentProps` utility detailed below. + +
+ +## Consuming Props of a Component with defaultProps + +A component with `defaultProps` may seem to have some required props that actually aren't. + +### Problem Statement + +Here's what you want to do: ```tsx -// //////////////// -// function components -// //////////////// -type GreetProps = { age: number }; +interface IProps { + name: string; +} +const defaultProps = { + age: 25, +}; +const GreetComponent = ({ name, age }: IProps & typeof defaultProps) => ( +
{`Hello, my name is ${name}, ${age}`}
+); +GreetComponent.defaultProps = defaultProps; -const Greet = ({ age = 21 }: GreetProps) => { - /*...*/ +const TestComponent = (props: React.ComponentProps) => { + return

; }; + +// Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }' +const el = ; ``` +### Solution + +Define a utility that applies `JSX.LibraryManagedAttributes`: + ```tsx -// //////////////// -// class components -// //////////////// -type GreetProps = { - age?: number; +type ComponentProps = T extends + | React.ComponentType + | React.Component + ? JSX.LibraryManagedAttributes + : never; + +const TestComponent = (props: ComponentProps) => { + return

; }; -class Greet extends React.Component { - const { age = 21 } = this.props - /*...*/ -} - -let el = ; +// No error +const el = ; ``` - +[_See this in TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAMImQB2W3MABWJhUAHgAqAPjgBeOOLhYAHjD4ATdNjwwAdJ3ARe-cSyyjg3AlihwB0gD6Yqu-Tz4xzl67cl04cAH44ACkAZQANHQAZYAAjKGQoJgBZZG5kAHMsNQBBGBgoOIBXVTFxABofPzgALjheADdrejoLVSgCPDYASSEIETgAb2r0kCw61AKLDPoAXzpcQ0m4NSxOooAbQWF0OWH-TPG4ACYAVnK6WfpF7mWAcUosGFdDd1k4AApB+uQxysO4LM6r0dnAAGRwZisCAEFZrZCbbb9VAASlk0g+1VEamADUkgwABgAJLAbDYQSogJg-MZwYDoAAkg1GWFmlSZh1mBNmogA9Di8XQUfQHlgni8jLpVustn0BnJpQjZTsWrzeXANsh2gwbstxFhJhK3nIPmAdnUjfw5WIoVgYXBReKuK9+JI0TJpPs4JQYEUoNw4KIABYARjgvN8VwYargADkIIooMQoAslvBSe8JAbns7JTSsDIyAQIBAyOHJDQgA) + +## Misc Discussions and Knowledge
Why does React.FC break defaultProps?