From e571821d3b27bb9e9fa6f06be141388a3de41155 Mon Sep 17 00:00:00 2001 From: swyx Date: Sat, 22 Aug 2020 10:01:44 +0800 Subject: [PATCH 1/6] address React.HTMLProps issue --- docs/advanced/patterns_by_usecase.md | 79 ++++++++++++++++++++++++++-- docs/advanced/types-react-ap.md | 1 + 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index 6355f9ef..fffaac41 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -10,14 +10,62 @@ sidebar_label: Useful Patterns by Use Case Usecase: you want to make a ` + + // OK + return ; +} +// 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 ; +} +``` + + + ## Wrapping/Mirroring a Component Usecase: same as above, but for a React Component you don't have access to diff --git a/docs/advanced/types-react-ap.md b/docs/advanced/types-react-ap.md index 1bd8e231..b4384e91 100644 --- a/docs/advanced/types-react-ap.md +++ b/docs/advanced/types-react-ap.md @@ -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`. From 771375e4ff9ef91a91d08f93f29eafb5b6f3ea92 Mon Sep 17 00:00:00 2001 From: swyx Date: Sat, 22 Aug 2020 10:08:18 +0800 Subject: [PATCH 2/6] small fix --- docs/advanced/patterns_by_usecase.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index fffaac41..387ac3ea 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -106,11 +106,9 @@ Usecase: same as above, but for a React Component you don't have access to ```tsx const Box = (props: React.CSSProperties) =>
; -const Card = ({ - title, - children, - ...props -}: { title: string } & $ElementProps) => ( +const Card = ( + { title, children, ...props }: { title: string } & $ElementProps // new utility, see below +) => ( {title}: {children} @@ -130,7 +128,7 @@ declare type $ElementProps = T extends React.ComponentType : never; ``` -Advanced Example: +Usage: ```tsx import * as Recompose from "recompose"; @@ -146,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 From 80ae7cc2f12890fa5da7bbe9e100018517cd383d Mon Sep 17 00:00:00 2001 From: swyx Date: Sat, 22 Aug 2020 10:08:49 +0800 Subject: [PATCH 3/6] small fix --- docs/advanced/patterns_by_usecase.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index 387ac3ea..c31ae228 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -99,7 +99,7 @@ function App() { -## Wrapping/Mirroring a Component +### Wrapping/Mirroring a Component Usecase: same as above, but for a React Component you don't have access to From 7142f304fc1ec052dd5b5b4f794f7f778b366acd Mon Sep 17 00:00:00 2001 From: swyx Date: Sat, 22 Aug 2020 10:10:41 +0800 Subject: [PATCH 4/6] small fix --- docs/advanced/types-react-ap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/types-react-ap.md b/docs/advanced/types-react-ap.md index b4384e91..95ad206f 100644 --- a/docs/advanced/types-react-ap.md +++ b/docs/advanced/types-react-ap.md @@ -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` From b2cbbeefd12d6da975088e285907a065b8e76555 Mon Sep 17 00:00:00 2001 From: swyx Date: Sun, 23 Aug 2020 00:50:43 +0800 Subject: [PATCH 5/6] add specialized HTMLAttributes --- docs/advanced/patterns_by_usecase.md | 9 ++++ docs/advanced/types-react-ap.md | 63 +++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index c31ae228..8b663bfc 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -35,6 +35,15 @@ export function Button(props: ButtonProps) { } ``` +You can also use specialized interfaces for each element if they are available to you: + +```tsx +// instead of `export interface ButtonProps extends btnType` +export interface ButtonProps extends React.ButtonHTMLAttributes +``` + +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). +
diff --git a/docs/advanced/types-react-ap.md b/docs/advanced/types-react-ap.md index 95ad206f..5c1fec0c 100644 --- a/docs/advanced/types-react-ap.md +++ b/docs/advanced/types-react-ap.md @@ -37,7 +37,68 @@ 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). +- `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 `JSX.IntrinsicElements` for [Wrapping/Mirroring a HTML Element](https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element), or [specialized HTMLAttributes interfaces](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/a2aa0406e7bf269eef01292fcb2b24dee89a7d2b/types/react/index.d.ts#L1914-L2625): + +
+ + + List of specialized HTMLAttributes + + + - `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` + +
+ - 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`. From 7d9a16a8cbbc773a51bb303bc0f084dcb3e39773 Mon Sep 17 00:00:00 2001 From: swyx Date: Sun, 23 Aug 2020 06:23:57 +0800 Subject: [PATCH 6/6] update to React.ComponentProps --- docs/advanced/patterns_by_usecase.md | 73 +++++++++++----------------- docs/advanced/types-react-ap.md | 8 +-- 2 files changed, 34 insertions(+), 47 deletions(-) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index 8b663bfc..f6fc881b 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -10,22 +10,20 @@ sidebar_label: Useful Patterns by Use Case Usecase: you want to make a ` + // Type '"foo"' is not assignable to type '"button" | "submit" | "reset" | undefined'.(2322) + // return - // OK - return ; + // no error + return ; } + // implementation -type btnType = JSX.IntrinsicElements["button"]; // cannot inline or will error -export interface ButtonProps extends btnType { +export interface ButtonProps extends React.ComponentProps<"button"> { specialProp?: string; } export function Button(props: ButtonProps) { @@ -35,69 +33,56 @@ export function Button(props: ButtonProps) { } ``` -You can also use specialized interfaces for each element if they are available to you: - -```tsx -// instead of `export interface ButtonProps extends btnType` -export interface ButtonProps extends React.ButtonHTMLAttributes -``` - -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). +[_See this in the TS Playground_](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQNwCwAUI4wPQtwCuqyA5lowQ4A7fMAhC4AQTBgAFAEo4Ab0Zw4bOABUAnmCzkARAQgQDZOMHRCI8NKmA8hyAEYAbfTAhwYu-WQPOHDCeQgZwAD5wBqgcziDAMGGRBpSoWIkRnEIAJlgEwEJY2WQAdLIATADM5eXyqurslDAcUBIAPABCQSHevgC8RiYGAHxwqK7ZANYAVnBtLF3B4sP19RrWcFhQxFD1TS3tiz0+egOBS6GjMFgAHvDzR8uMAL7MDBqgYO4gWEIwyDAxEJGLdILALH8tgQ8PpHkIAArEMDoW7XHLobB4GAlADCJEghT+iIgyLaZHOITIoxUDDUqD0uGAyFcxLAAH4AFxjGBQAo8egMV4MUHQQjCUTiOBw2RgJGoLlw1moRQ0tS4cSoeBKMYMpkspEAGjgJRNqXgzzgfTgspJqAFag02S8qBI6QAFny4AB3BJunVYRnM1l7dIHOYUyVKE0lM0WljDAXPIA)
-Why not `React.HTMLProps` or `HTMLAttributes`? +Why not `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes` or `React.HTMLProps` or `React.HTMLAttributes`? -This is what happens when you use `React.HTMLProps`: +### Using `JSX.IntrinsicElements` or `React.[Element]HTMLAttributes` + +There are at least 2 other equivalent ways to do this: ```tsx -export interface ButtonProps extends React.HTMLProps { - specialProp: string; -} -export function Button(props: ButtonProps) { - const { specialProp, ...rest } = props; - // ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'. - return
- -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` you will be unable to type its `onChange` event handler prop, as opposed to `DetailedHTMLAttributes, HTMLButtonElement>`. +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 { - specialProp: string; -} -export function Button(props: ButtonProps) { - const { specialProp, ...rest } = props; - return
+ Note that there are about 50 of these, which means there are some HTML elements which are not covered. + - `AnchorHTMLAttributes` - `AudioHTMLAttributes` - `AreaHTMLAttributes` @@ -107,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` may be more readable as it doesn't require knowing what "Intrinsic" means. Not commonly used but good to know