Skip to content

address React.HTMLProps issue #276

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 6 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
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
89 changes: 77 additions & 12 deletions docs/advanced/patterns_by_usecase.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,99 @@ sidebar_label: Useful Patterns by Use Case

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

Strategy: extend `React.HTMLProps<YOURELEMENTHERE>`
Strategy: extend `React.ComponentProps<'button'>`

Example:
```tsx
// usage
function App() {
// Type '"foo"' is not assignable to type '"button" | "submit" | "reset" | undefined'.(2322)
// return <Button type="foo"> sldkj </Button>

// no error
return <Button type="button"> text </Button>;
}

// implementation
export interface ButtonProps extends React.ComponentProps<"button"> {
specialProp?: string;
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
// do something with specialProp
return <button {...rest} />;
}
```

[_See this in the TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQNwCwAUI4wPQtwCuqyA5lowQ4A7fMAhC4AQTBgAFAEo4Ab0Zw4bOABUAnmCzkARAQgQDZOMHRCI8NKmA8hyAEYAbfTAhwYu-WQPOHDCeQgZwAD5wBqgcziDAMGGRBpSoWIkRnEIAJlgEwEJY2WQAdLIATADM5eXyqurslDAcUBIAPABCQSHevgC8RiYGAHxwqK7ZANYAVnBtLF3B4sP19RrWcFhQxFD1TS3tiz0+egOBS6GjMFgAHvDzR8uMAL7MDBqgYO4gWEIwyDAxEJGLdILALH8tgQ8PpHkIAArEMDoW7XHLobB4GAlADCJEghT+iIgyLaZHOITIoxUDDUqD0uGAyFcxLAAH4AFxjGBQAo8egMV4MUHQQjCUTiOBw2RgJGoLlw1moRQ0tS4cSoeBKMYMpkspEAGjgJRNqXgzzgfTgspJqAFag02S8qBI6QAFny4AB3BJunVYRnM1l7dIHOYUyVKE0lM0WljDAXPIA)

<details>
<summary>

Why not `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes` or `React.HTMLProps` or `React.HTMLAttributes`?

</summary>

### Using `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes`

There are at least 2 other equivalent ways to do this:

```tsx
// Method 1: JSX.IntrinsicElements
type btnType = JSX.IntrinsicElements["button"]; // cannot inline or will error
export interface ButtonProps extends btnType {} // etc

// Method 2: React.[Element]HTMLAttributes
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
```

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.

> 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).

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.

### Definitely not `React.HTMLProps` or `React.HTMLAttributes`

This is what happens when you use `React.HTMLProps`:

```tsx
export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
specialProp: string;
type: "button" | "submit" | "reset"; // flaw of React.HTMLProps
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
// do something with specialProp
// ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.
return <button {...rest} />;
}
```

## Wrapping/Mirroring a Component
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).

This is what happens when you use `React.HTMLAttributes`:

```tsx
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
/* etc */
}
// usage
function App() {
// Property 'type' does not exist on type 'IntrinsicAttributes & ButtonProps'
return <Button type="submit"> text </Button>;
}
```

</details>

### Wrapping/Mirroring a Component

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

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

const Card = ({
title,
children,
...props
}: { title: string } & $ElementProps<typeof Box>) => (
const Card = (
{ title, children, ...props }: { title: string } & $ElementProps<typeof Box> // new utility, see below
) => (
<Box {...props}>
{title}: {children}
</Box>
Expand All @@ -57,7 +122,7 @@ declare type $ElementProps<T> = T extends React.ComponentType<infer Props>
: never;
```

Advanced Example:
Usage:

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

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

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

## Polymorphic Components

Expand Down
70 changes: 67 additions & 3 deletions docs/advanced/types-react-ap.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,79 @@ Most Commonly Used Interfaces and Types
Not Commonly Used but Good to know

- `Ref` - used to type `innerRef`
- `ElementType` - used for higher order components or operations on components
- `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)
- `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
- `ComponentType` - used for higher order components where you don't specifically deal with the intrinsic components
- `ReactPortal` - used if you specifically need to type a prop as a portal, otherwise it is part of `ReactNode`
- `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
- `JSXElementConstructor` - anything that TypeScript considers to be a valid thing that can go into the opening tag of a JSX expression
- `ComponentProps` - props of a component
- `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)
- `ComponentPropsWithRef` - props of a component where if it is a class-based component it will replace the `ref` prop with its own instance type
- `ComponentPropsWithoutRef` - props of a component without its `ref` prop
- `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):

<details>

<summary>
List of specialized HTMLAttributes
</summary>

Note that there are about 50 of these, which means there are some HTML elements which are not covered.

- `AnchorHTMLAttributes`
- `AudioHTMLAttributes`
- `AreaHTMLAttributes`
- `BaseHTMLAttributes`
- `BlockquoteHTMLAttributes`
- `ButtonHTMLAttributes`
- `CanvasHTMLAttributes`
- `ColHTMLAttributes`
- `ColgroupHTMLAttributes`
- `DataHTMLAttributes`
- `DetailsHTMLAttributes`
- `DelHTMLAttributes`
- `DialogHTMLAttributes`
- `EmbedHTMLAttributes`
- `FieldsetHTMLAttributes`
- `FormHTMLAttributes`
- `HtmlHTMLAttributes`
- `IframeHTMLAttributes`
- `ImgHTMLAttributes`
- `InsHTMLAttributes`
- `InputHTMLAttributes`
- `KeygenHTMLAttributes`
- `LabelHTMLAttributes`
- `LiHTMLAttributes`
- `LinkHTMLAttributes`
- `MapHTMLAttributes`
- `MenuHTMLAttributes`
- `MediaHTMLAttributes`
- `MetaHTMLAttributes`
- `MeterHTMLAttributes`
- `QuoteHTMLAttributes`
- `ObjectHTMLAttributes`
- `OlHTMLAttributes`
- `OptgroupHTMLAttributes`
- `OptionHTMLAttributes`
- `OutputHTMLAttributes`
- `ParamHTMLAttributes`
- `ProgressHTMLAttributes`
- `SlotHTMLAttributes`
- `ScriptHTMLAttributes`
- `SelectHTMLAttributes`
- `SourceHTMLAttributes`
- `StyleHTMLAttributes`
- `TableHTMLAttributes`
- `TextareaHTMLAttributes`
- `TdHTMLAttributes`
- `ThHTMLAttributes`
- `TimeHTMLAttributes`
- `TrackHTMLAttributes`
- `VideoHTMLAttributes`
- `WebViewHTMLAttributes`

</details>

- all methods: `createElement`, `cloneElement`, ... are all public and reflect the React runtime API

[@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`.
Expand All @@ -45,7 +109,7 @@ Not Commonly Used but Good to know

- `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).
- `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.
- `IntrinsicElements` - every possible built-in component that can be typed in as a lowercase tag name in JSX
- `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.

Not commonly used but good to know

Expand Down