From a74696903a3de7a87caf3c618937273a2abbb194 Mon Sep 17 00:00:00 2001 From: swyx Date: Sun, 23 Aug 2020 02:15:42 +0800 Subject: [PATCH 1/3] update notes on defaultProps --- docs/basic/getting-started/default-props.md | 86 ++++++++++++++------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/docs/basic/getting-started/default-props.md b/docs/basic/getting-started/default-props.md index 7bb7c944..4c045985 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,41 +73,35 @@ let el = ; ```
- An alternative approach - -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. + + + `JSX.LibraryManagedAttributes` nuance for library authors + + -```tsx -// //////////////// -// function components -// //////////////// -type GreetProps = { age: number }; +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`. -const Greet = ({ age = 21 }: GreetProps) => { - /*...*/ -}; -``` +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: ```tsx -// //////////////// -// class components -// //////////////// -type GreetProps = { +// internal contract, should not be exported out +type GreetProps = { age?: number; }; -class Greet extends React.Component { - const { age = 21 } = this.props - /*...*/ +class Greet extends Component { + static defaultProps = { age: 21 }; } -let el = ; +// external contract +export type ApparentGreetProps = JSX.LibraryManagedAttributes< + typeof Greet, + GreetProps +>; ``` +This will work properly, although hovering over `ApparentGreetProps` may be a little intimidating. +
From 6c8cbdda5f5a3ed2587545ffd5f4742bc036dbcd Mon Sep 17 00:00:00 2001 From: swyx Date: Sun, 23 Aug 2020 02:28:42 +0800 Subject: [PATCH 2/3] add componentType utility on defaultprops page --- docs/basic/getting-started/default-props.md | 51 ++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/basic/getting-started/default-props.md b/docs/basic/getting-started/default-props.md index 4c045985..30f379ec 100644 --- a/docs/basic/getting-started/default-props.md +++ b/docs/basic/getting-started/default-props.md @@ -100,10 +100,59 @@ export type ApparentGreetProps = JSX.LibraryManagedAttributes< >; ``` -This will work properly, although hovering over `ApparentGreetProps` may be a little intimidating. +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 +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 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 +type ComponentProps = T extends + | React.ComponentType + | React.Component + ? JSX.LibraryManagedAttributes + : never; + +const TestComponent = (props: ComponentProps) => { + return

; +}; + +// No error +const el = ; +``` + +## Misc Discussions and Knowledge +
Why does React.FC break defaultProps? From ef58dd5f00e82f5e55cf7bdc0fa08c7202d8980e Mon Sep 17 00:00:00 2001 From: swyx Date: Sun, 23 Aug 2020 02:29:11 +0800 Subject: [PATCH 3/3] add ts playground --- docs/basic/getting-started/default-props.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/basic/getting-started/default-props.md b/docs/basic/getting-started/default-props.md index 30f379ec..5a2ffdcd 100644 --- a/docs/basic/getting-started/default-props.md +++ b/docs/basic/getting-started/default-props.md @@ -100,6 +100,7 @@ export type ApparentGreetProps = JSX.LibraryManagedAttributes< >; ``` +`` This will work properly, although hovering over `ApparentGreetProps` may be a little intimidating. You can reduce this boilerplate with the `ComponentProps` utility detailed below.
@@ -151,6 +152,8 @@ const TestComponent = (props: ComponentProps) => { 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