Skip to content

Commit 2201cea

Browse files
swyxioswyx
and
swyx
authored
address React.HTMLProps issue (#276)
Co-authored-by: swyx <wanshawn@amazon.com>
1 parent 05bbc55 commit 2201cea

File tree

2 files changed

+144
-15
lines changed

2 files changed

+144
-15
lines changed

docs/advanced/patterns_by_usecase.md

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,99 @@ sidebar_label: Useful Patterns by Use Case
1010

1111
Usecase: you want to make a `<Button>` that takes all the normal props of `<button>` and does extra stuff.
1212

13-
Strategy: extend `React.HTMLProps<YOURELEMENTHERE>`
13+
Strategy: extend `React.ComponentProps<'button'>`
1414

15-
Example:
15+
```tsx
16+
// usage
17+
function App() {
18+
// Type '"foo"' is not assignable to type '"button" | "submit" | "reset" | undefined'.(2322)
19+
// return <Button type="foo"> sldkj </Button>
20+
21+
// no error
22+
return <Button type="button"> text </Button>;
23+
}
24+
25+
// implementation
26+
export interface ButtonProps extends React.ComponentProps<"button"> {
27+
specialProp?: string;
28+
}
29+
export function Button(props: ButtonProps) {
30+
const { specialProp, ...rest } = props;
31+
// do something with specialProp
32+
return <button {...rest} />;
33+
}
34+
```
35+
36+
[_See this in the TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQNwCwAUI4wPQtwCuqyA5lowQ4A7fMAhC4AQTBgAFAEo4Ab0Zw4bOABUAnmCzkARAQgQDZOMHRCI8NKmA8hyAEYAbfTAhwYu-WQPOHDCeQgZwAD5wBqgcziDAMGGRBpSoWIkRnEIAJlgEwEJY2WQAdLIATADM5eXyqurslDAcUBIAPABCQSHevgC8RiYGAHxwqK7ZANYAVnBtLF3B4sP19RrWcFhQxFD1TS3tiz0+egOBS6GjMFgAHvDzR8uMAL7MDBqgYO4gWEIwyDAxEJGLdILALH8tgQ8PpHkIAArEMDoW7XHLobB4GAlADCJEghT+iIgyLaZHOITIoxUDDUqD0uGAyFcxLAAH4AFxjGBQAo8egMV4MUHQQjCUTiOBw2RgJGoLlw1moRQ0tS4cSoeBKMYMpkspEAGjgJRNqXgzzgfTgspJqAFag02S8qBI6QAFny4AB3BJunVYRnM1l7dIHOYUyVKE0lM0WljDAXPIA)
37+
38+
<details>
39+
<summary>
40+
41+
Why not `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes` or `React.HTMLProps` or `React.HTMLAttributes`?
42+
43+
</summary>
44+
45+
### Using `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes`
46+
47+
There are at least 2 other equivalent ways to do this:
48+
49+
```tsx
50+
// Method 1: JSX.IntrinsicElements
51+
type btnType = JSX.IntrinsicElements["button"]; // cannot inline or will error
52+
export interface ButtonProps extends btnType {} // etc
53+
54+
// Method 2: React.[Element]HTMLAttributes
55+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
56+
```
57+
58+
Looking at [the source for `ComponentProps`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f3134f4897c8473f590cbcdd5788da8d59796f45/types/react/index.d.ts#L821) shows that this is a clever wrapper for `JSX.IntrinsicElements`, whereas the second method relies on specialized interfaces with unfamiliar naming/capitalization.
59+
60+
> Note: There are over 50 of these specialized interfaces available - look for `HTMLAttributes` in our [`@types/react` commentary](https://react-typescript-cheatsheet.netlify.app/docs/advanced/types_react_api#typesreact).
61+
62+
Ultimately, [we picked the `ComponentProps` method](https://github.com/typescript-cheatsheets/react/pull/276/files) as it involves the least TS specific jargon and has the most ease of use. But you'll be fine with either of these methods if you prefer.
63+
64+
### Definitely not `React.HTMLProps` or `React.HTMLAttributes`
65+
66+
This is what happens when you use `React.HTMLProps`:
1667
1768
```tsx
1869
export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
1970
specialProp: string;
20-
type: "button" | "submit" | "reset"; // flaw of React.HTMLProps
2171
}
2272
export function Button(props: ButtonProps) {
2373
const { specialProp, ...rest } = props;
24-
// do something with specialProp
74+
// ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.
2575
return <button {...rest} />;
2676
}
2777
```
2878

29-
## Wrapping/Mirroring a Component
79+
It infers a too-wide type of `string` for `type`, because it [uses `AllHTMLAttributes` under the hood](https://github.com/typescript-cheatsheets/react/issues/128#issuecomment-508103558).
80+
81+
This is what happens when you use `React.HTMLAttributes`:
82+
83+
```tsx
84+
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
85+
/* etc */
86+
}
87+
// usage
88+
function App() {
89+
// Property 'type' does not exist on type 'IntrinsicAttributes & ButtonProps'
90+
return <Button type="submit"> text </Button>;
91+
}
92+
```
93+
94+
</details>
95+
96+
### Wrapping/Mirroring a Component
3097

3198
Usecase: same as above, but for a React Component you don't have access to
3299

33100
```tsx
34101
const Box = (props: React.CSSProperties) => <div style={props} />;
35102

36-
const Card = ({
37-
title,
38-
children,
39-
...props
40-
}: { title: string } & $ElementProps<typeof Box>) => (
103+
const Card = (
104+
{ title, children, ...props }: { title: string } & $ElementProps<typeof Box> // new utility, see below
105+
) => (
41106
<Box {...props}>
42107
{title}: {children}
43108
</Box>
@@ -57,7 +122,7 @@ declare type $ElementProps<T> = T extends React.ComponentType<infer Props>
57122
: never;
58123
```
59124

60-
Advanced Example:
125+
Usage:
61126

62127
```tsx
63128
import * as Recompose from "recompose";
@@ -73,7 +138,7 @@ export const defaultProps = <
73138

74139
_thanks [dmisdm](https://github.com/typescript-cheatsheets/react/issues/23)_
75140

76-
\*TODO: check how this conflicts/merges/duplicates with the Troubleshooting Handbook "Types I need weren't Exported" advice
141+
_TODO: check how this conflicts/merges/duplicates with the Troubleshooting Handbook "Types I need weren't Exported" advice_
77142

78143
## Polymorphic Components
79144

docs/advanced/types-react-ap.md

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,79 @@ Most Commonly Used Interfaces and Types
2828
Not Commonly Used but Good to know
2929

3030
- `Ref` - used to type `innerRef`
31-
- `ElementType` - used for higher order components or operations on components
31+
- `ElementType` - used for higher order components or operations on components, e.g. [Polymorphic Components](https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase#polymorphic-components)
3232
- `ReactElement` - [can be used if you want to pass it to `cloneElement`](https://www.reddit.com/r/reactjs/comments/ia8sdi/any_other_typescript_users_constantly_confused/g1npahe/) aka it's pretty rarely used
3333
- `ComponentType` - used for higher order components where you don't specifically deal with the intrinsic components
3434
- `ReactPortal` - used if you specifically need to type a prop as a portal, otherwise it is part of `ReactNode`
3535
- `ComponentClass` - a complete interface for the produced constructor function of a class declaration that extends `Component`, often used to type external components instead of typing your own
3636
- `JSXElementConstructor` - anything that TypeScript considers to be a valid thing that can go into the opening tag of a JSX expression
37-
- `ComponentProps` - props of a component
37+
- `ComponentProps` - props of a component - most useful for [Wrapping/Mirroring a HTML Element](https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element)
3838
- `ComponentPropsWithRef` - props of a component where if it is a class-based component it will replace the `ref` prop with its own instance type
3939
- `ComponentPropsWithoutRef` - props of a component without its `ref` prop
40+
- `HTMLProps` and `HTMLAttributes` - these are the most generic versions, for global attributes (see a list of [attributes marked as "global attribute" on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes)). In general, prefer `React.ComponentProps`, `JSX.IntrinsicElements`, or [specialized HTMLAttributes interfaces](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/a2aa0406e7bf269eef01292fcb2b24dee89a7d2b/types/react/index.d.ts#L1914-L2625):
41+
42+
<details>
43+
44+
<summary>
45+
List of specialized HTMLAttributes
46+
</summary>
47+
48+
Note that there are about 50 of these, which means there are some HTML elements which are not covered.
49+
50+
- `AnchorHTMLAttributes`
51+
- `AudioHTMLAttributes`
52+
- `AreaHTMLAttributes`
53+
- `BaseHTMLAttributes`
54+
- `BlockquoteHTMLAttributes`
55+
- `ButtonHTMLAttributes`
56+
- `CanvasHTMLAttributes`
57+
- `ColHTMLAttributes`
58+
- `ColgroupHTMLAttributes`
59+
- `DataHTMLAttributes`
60+
- `DetailsHTMLAttributes`
61+
- `DelHTMLAttributes`
62+
- `DialogHTMLAttributes`
63+
- `EmbedHTMLAttributes`
64+
- `FieldsetHTMLAttributes`
65+
- `FormHTMLAttributes`
66+
- `HtmlHTMLAttributes`
67+
- `IframeHTMLAttributes`
68+
- `ImgHTMLAttributes`
69+
- `InsHTMLAttributes`
70+
- `InputHTMLAttributes`
71+
- `KeygenHTMLAttributes`
72+
- `LabelHTMLAttributes`
73+
- `LiHTMLAttributes`
74+
- `LinkHTMLAttributes`
75+
- `MapHTMLAttributes`
76+
- `MenuHTMLAttributes`
77+
- `MediaHTMLAttributes`
78+
- `MetaHTMLAttributes`
79+
- `MeterHTMLAttributes`
80+
- `QuoteHTMLAttributes`
81+
- `ObjectHTMLAttributes`
82+
- `OlHTMLAttributes`
83+
- `OptgroupHTMLAttributes`
84+
- `OptionHTMLAttributes`
85+
- `OutputHTMLAttributes`
86+
- `ParamHTMLAttributes`
87+
- `ProgressHTMLAttributes`
88+
- `SlotHTMLAttributes`
89+
- `ScriptHTMLAttributes`
90+
- `SelectHTMLAttributes`
91+
- `SourceHTMLAttributes`
92+
- `StyleHTMLAttributes`
93+
- `TableHTMLAttributes`
94+
- `TextareaHTMLAttributes`
95+
- `TdHTMLAttributes`
96+
- `ThHTMLAttributes`
97+
- `TimeHTMLAttributes`
98+
- `TrackHTMLAttributes`
99+
- `VideoHTMLAttributes`
100+
- `WebViewHTMLAttributes`
101+
102+
</details>
103+
40104
- all methods: `createElement`, `cloneElement`, ... are all public and reflect the React runtime API
41105

42106
[@Ferdaber's note](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/pull/69): I discourage the use of most `...Element` types because of how black-boxy `JSX.Element` is. You should almost always assume that anything produced by `React.createElement` is the base type `React.ReactElement`.
@@ -45,7 +109,7 @@ Not Commonly Used but Good to know
45109

46110
- `Element` - the type of any JSX expression. You should ideally never need to see or use this, but you do because of [a limitation of TypeScript](https://github.com/microsoft/TypeScript/issues/21699).
47111
- `LibraryManagedAttributes` - It specifies other places where JSX elements can declare and initialize property types. Used to resolve static `defaultProps` and `propTypes` with the internal props type of a component.
48-
- `IntrinsicElements` - every possible built-in component that can be typed in as a lowercase tag name in JSX
112+
- `IntrinsicElements` - every possible built-in component that can be typed in as a lowercase tag name in JSX. If you're using this to get the attributes for a HTML element, `React.ComponentProps<element>` may be more readable as it doesn't require knowing what "Intrinsic" means.
49113

50114
Not commonly used but good to know
51115

0 commit comments

Comments
 (0)