Skip to content

update notes on defaultProps #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 26, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 106 additions & 28 deletions docs/basic/getting-started/default-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<GreetProps> {
const { age = 21 } = this.props
/*...*/
}

let el = <Greet age={3} />;
```

## 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
Expand All @@ -41,42 +73,88 @@ let el = <Greet age={3} />;
```

<details>
<summary>An alternative approach</summary>
<summary>

`JSX.LibraryManagedAttributes` nuance for library authors

</summary>

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<GreetProps> {
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.

</details>

## 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) => (
<div>{`Hello, my name is ${name}, ${age}`}</div>
);
GreetComponent.defaultProps = defaultProps;

const Greet = ({ age = 21 }: GreetProps) => {
/*...*/
const TestComponent = (props: React.ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};

// Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }'
const el = <TestComponent name="foo" />;
```

### Solution

Define a utility that applies `JSX.LibraryManagedAttributes`:

```tsx
// ////////////////
// class components
// ////////////////
type GreetProps = {
age?: number;
type ComponentProps<T> = T extends
| React.ComponentType<infer P>
| React.Component<infer P>
? JSX.LibraryManagedAttributes<T, P>
: never;

const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};

class Greet extends React.Component<GreetProps> {
const { age = 21 } = this.props
/*...*/
}

let el = <Greet age={3} />;
// No error
const el = <TestComponent name="foo" />;
```

</details>
[_See this in TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAMImQB2W3MABWJhUAHgAqAPjgBeOOLhYAHjD4ATdNjwwAdJ3ARe-cSyyjg3AlihwB0gD6Yqu-Tz4xzl67cl04cAH44ACkAZQANHQAZYAAjKGQoJgBZZG5kAHMsNQBBGBgoOIBXVTFxABofPzgALjheADdrejoLVSgCPDYASSEIETgAb2r0kCw61AKLDPoAXzpcQ0m4NSxOooAbQWF0OWH-TPG4ACYAVnK6WfpF7mWAcUosGFdDd1k4AApB+uQxysO4LM6r0dnAAGRwZisCAEFZrZCbbb9VAASlk0g+1VEamADUkgwABgAJLAbDYQSogJg-MZwYDoAAkg1GWFmlSZh1mBNmogA9Di8XQUfQHlgni8jLpVustn0BnJpQjZTsWrzeXANsh2gwbstxFhJhK3nIPmAdnUjfw5WIoVgYXBReKuK9+JI0TJpPs4JQYEUoNw4KIABYARjgvN8VwYargADkIIooMQoAslvBSe8JAbns7JTSsDIyAQIBAyOHJDQgA)

## Misc Discussions and Knowledge

<details>
<summary>Why does React.FC break defaultProps?</summary>
Expand Down