Skip to content

Commit 49df823

Browse files
swyxioswyx
and
swyx
authored
update notes on defaultProps (#277)
Co-authored-by: swyx <[email protected]>
1 parent c8fec14 commit 49df823

File tree

1 file changed

+106
-28
lines changed

1 file changed

+106
-28
lines changed

Diff for: docs/basic/getting-started/default-props.md

+106-28
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,57 @@ id: default_props
33
title: Typing defaultProps
44
---
55

6-
## Typing defaultProps
6+
## You May Not Need `defaultProps`
77

8-
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`.
8+
As per [this tweet](https://twitter.com/dan_abramov/status/1133878326358171650), defaultProps will eventually be deprecated. You can check the discussions here:
9+
10+
- https://twitter.com/hswolff/status/1133759319571345408
11+
12+
The consensus is to use object default values.
13+
14+
Function Components:
15+
16+
```tsx
17+
type GreetProps = { age?: number };
18+
19+
const Greet = ({ age = 21 }: GreetProps) => // etc
20+
```
21+
22+
Class Components:
23+
24+
```tsx
25+
type GreetProps = {
26+
age?: number;
27+
};
28+
29+
class Greet extends React.Component<GreetProps> {
30+
const { age = 21 } = this.props
31+
/*...*/
32+
}
33+
34+
let el = <Greet age={3} />;
35+
```
36+
37+
## Typing `defaultProps`
38+
39+
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).
40+
41+
**Function Components**
942

1043
```tsx
11-
// ////////////////
12-
// function components
13-
// ////////////////
1444
type GreetProps = { age: number } & typeof defaultProps;
1545
const defaultProps = {
1646
age: 21,
1747
};
1848

1949
const Greet = (props: GreetProps) => {
20-
/*...*/
50+
// etc
2151
};
2252
Greet.defaultProps = defaultProps;
2353
```
2454

55+
_[See this in TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAOKVYwAKxY6ALxwA3igDmWAFxwAdgFcQAIyxQ4AXzgAyOM1YQCcACZYCyeQBte-VPVwRZqeCbOXrEAXGEi6cCdLgAJgBGABo6dXo6e0d4TixuLzgACjAbGXjuPg9UAEovAD5RXzhKGHkoWTgAHiNgADcCkTScgDpkSTgAeiQFZVVELvVqrrrGiPpMmFaXcytsz2FZtwXbOiA)_
56+
2557
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:
2658

2759
```tsx
@@ -41,42 +73,88 @@ let el = <Greet age={3} />;
4173
```
4274

4375
<details>
44-
<summary>An alternative approach</summary>
76+
<summary>
77+
78+
`JSX.LibraryManagedAttributes` nuance for library authors
79+
80+
</summary>
4581

46-
As per [this tweet](https://twitter.com/dan_abramov/status/1133878326358171650), defaultProps will eventually be deprecated. You can check the discussions here:
82+
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`.
4783

48-
- https://twitter.com/hswolff/status/1133759319571345408
84+
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:
4985

50-
The consensus is to use object default values.
86+
```tsx
87+
// internal contract, should not be exported out
88+
type GreetProps = {
89+
age?: number;
90+
};
91+
92+
class Greet extends Component<GreetProps> {
93+
static defaultProps = { age: 21 };
94+
}
95+
96+
// external contract
97+
export type ApparentGreetProps = JSX.LibraryManagedAttributes<
98+
typeof Greet,
99+
GreetProps
100+
>;
101+
```
102+
103+
``
104+
This will work properly, although hovering over `ApparentGreetProps` may be a little intimidating. You can reduce this boilerplate with the `ComponentProps` utility detailed below.
105+
106+
</details>
107+
108+
## Consuming Props of a Component with defaultProps
109+
110+
A component with `defaultProps` may seem to have some required props that actually aren't.
111+
112+
### Problem Statement
113+
114+
Here's what you want to do:
51115

52116
```tsx
53-
// ////////////////
54-
// function components
55-
// ////////////////
56-
type GreetProps = { age: number };
117+
interface IProps {
118+
name: string;
119+
}
120+
const defaultProps = {
121+
age: 25,
122+
};
123+
const GreetComponent = ({ name, age }: IProps & typeof defaultProps) => (
124+
<div>{`Hello, my name is ${name}, ${age}`}</div>
125+
);
126+
GreetComponent.defaultProps = defaultProps;
57127

58-
const Greet = ({ age = 21 }: GreetProps) => {
59-
/*...*/
128+
const TestComponent = (props: React.ComponentProps<typeof GreetComponent>) => {
129+
return <h1 />;
60130
};
131+
132+
// Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }'
133+
const el = <TestComponent name="foo" />;
61134
```
62135

136+
### Solution
137+
138+
Define a utility that applies `JSX.LibraryManagedAttributes`:
139+
63140
```tsx
64-
// ////////////////
65-
// class components
66-
// ////////////////
67-
type GreetProps = {
68-
age?: number;
141+
type ComponentProps<T> = T extends
142+
| React.ComponentType<infer P>
143+
| React.Component<infer P>
144+
? JSX.LibraryManagedAttributes<T, P>
145+
: never;
146+
147+
const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {
148+
return <h1 />;
69149
};
70150

71-
class Greet extends React.Component<GreetProps> {
72-
const { age = 21 } = this.props
73-
/*...*/
74-
}
75-
76-
let el = <Greet age={3} />;
151+
// No error
152+
const el = <TestComponent name="foo" />;
77153
```
78154

79-
</details>
155+
[_See this in TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAMImQB2W3MABWJhUAHgAqAPjgBeOOLhYAHjD4ATdNjwwAdJ3ARe-cSyyjg3AlihwB0gD6Yqu-Tz4xzl67cl04cAH44ACkAZQANHQAZYAAjKGQoJgBZZG5kAHMsNQBBGBgoOIBXVTFxABofPzgALjheADdrejoLVSgCPDYASSEIETgAb2r0kCw61AKLDPoAXzpcQ0m4NSxOooAbQWF0OWH-TPG4ACYAVnK6WfpF7mWAcUosGFdDd1k4AApB+uQxysO4LM6r0dnAAGRwZisCAEFZrZCbbb9VAASlk0g+1VEamADUkgwABgAJLAbDYQSogJg-MZwYDoAAkg1GWFmlSZh1mBNmogA9Di8XQUfQHlgni8jLpVustn0BnJpQjZTsWrzeXANsh2gwbstxFhJhK3nIPmAdnUjfw5WIoVgYXBReKuK9+JI0TJpPs4JQYEUoNw4KIABYARjgvN8VwYargADkIIooMQoAslvBSe8JAbns7JTSsDIyAQIBAyOHJDQgA)
156+
157+
## Misc Discussions and Knowledge
80158

81159
<details>
82160
<summary>Why does React.FC break defaultProps?</summary>

0 commit comments

Comments
 (0)