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 4 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
93 changes: 82 additions & 11 deletions docs/advanced/patterns_by_usecase.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,62 @@ 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 `JSX.IntrinsicElements['elementtype']`

Example:
Example ([Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQNwCwAUIwPTNyhgA2WIWAdjGQxgEPoxgBPMFjgAjGHwAqUmQF44AKQDKADQB0ASQFRgfVMFwBRbrwGoA2mVkBXGDFFkAuoywAPSLDsAlhQBHgyAEKu7nwACsRg6H4w-AAm6PJKKnAA3oxwcKjSuMDInPEQYAD8AFyFMCZ8AOb0DAC+Pv7QhM58+CJ8cFFuogAUYAmodcMxFYkAlLn5cLiiqPA5hcWl5QkANHB6R5TrcG1w6hOVqK0FrHCpEIUkWDAAFqZNcADuwO9bWBKZTmy0oMGcUEGAB4XCNBjkjnoTjBzswAHytDpMBj3ZyoZBNLCMAi9fqiOAAQTAYFGizyDDubGU0nIACJTAA3MrAVKssjsdB8CDwNDmJp8ZCybhwdwy7JkVmwmKsuAAHzgrNQzlkID+KvVrJOr31cF6qSwBFMWFSZD0owATABme32+bLe5giHQmbkyTSVTsvhczg81lowqcVIAawAVnAocwfXw0e62AB5ADSoNeXvjSbl-s12t1MDDMuS8cT0VEKfaQA)):

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

// OK
return <Button type="submit"> text </Button>;
}
// implementation
type btnType = JSX.IntrinsicElements["button"]; // cannot inline or will error
export interface ButtonProps extends btnType {
specialProp?: string;
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
// do something with specialProp
return <button {...rest} />;
}
```

<details>
<summary>

Why not `React.HTMLProps` or `HTMLAttributes`?

</summary>

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;
// ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.
return <button {...rest} />;
}
```

It infers a too-wide type of `string` for `type`.

<details>
<summary>
You can remedy this but it's a little ugly.
</summary>

```tsx
export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
specialProp: string;
type?: "button" | "submit" | "reset"; // override React.HTMLProps
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
Expand All @@ -26,18 +74,41 @@ export function Button(props: ButtonProps) {
}
```

## Wrapping/Mirroring a Component
</details>

More reasons per [@ferdaber](https://github.com/typescript-cheatsheets/react/issues/128#issuecomment-508103558):

> `HTMLProps` uses `AllHTMLAttributes`, which is the supertype of all HTML attributes, it is used for HTML elements which you don't the specific tag name for, and so is less specific than the ones defined in `JSX.IntrinsicElements`. For example when using `HTMLProps<HTMLButtonElement>` you will be unable to type its `onChange` event handler prop, as opposed to `DetailedHTMLAttributes<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>`.

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

```tsx
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
specialProp: string;
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
return <button {...rest} />;
}
// 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 +128,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 +144,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
3 changes: 2 additions & 1 deletion docs/advanced/types-react-ap.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ 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`
Expand All @@ -37,6 +37,7 @@ Not Commonly Used but Good to know
- `ComponentProps` - props of a component
- `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` - in general, prefer `JSX.IntrinsicElements` for [Wrapping/Mirroring a HTML Element](https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element).
- 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 Down