diff --git a/README.md b/README.md index 0171486f..a21bb38e 100644 --- a/README.md +++ b/README.md @@ -1177,18 +1177,32 @@ type AppProps = { }; ``` -Notice we have used the TSDoc `/** comment */` style here on each prop. You can and are encouraged to leave descriptive comments on reusable components. For a fuller example and discussion, see our [Commenting Components](https://react-typescript-cheatsheet.netlify.app/docs/advanced/misc_concerns/#commenting-components) section in the Advanced Cheatsheet. +##### `object` as the non-primitive type -
-More on object types: object, {"{}"}, etc +`object` is a common source of misunderstanding in TypeScript. It does not mean "any object" but rather "any non-primitive type", which means it represents anything that is not `number`, `string`, `boolean`, `symbol`, `null` or `undefined`. -In Typescript, it's generally best to use specific types for objects. In most cases, this means a literal type like { id: string; name: string }. In cases where there isn't a fixed structure for an object, you likely either want an index signature (possibly with the Record shorthand) - if there are values of a certain type, but the keys can change - or else generics - if the object structure is more-or-less an arbitrary black-box. +Typing "any non-primitive value" is most likely not something that you should do much in React, which means you will probably not use `object` much. -Another approach to objects is the Map data structure, but this is somewhat uncommon to use in React, because React prefers data to be changed immutably (e.g. setUser({...user, name: newName})), while Maps are mutable data structures. +##### Empty interface, `{}` and `Object` -"Vague" object types like object, {} are fairly niche and should be rarely used, and may function differently than you expect. object is any non-primitive value: this includes things like functions and arrays and constructors, not just "simple" objects. And {} is perhaps better thought of as "an interface with no required properties", not "an empty object" - in practice this type allows anything except null or undefined. Object behaves the same as {} and is basically never used. +An empty interface, `{}` and `Object` all represent "any non-nullish value"—not "an empty object" as you might think. [Using these types is a common source of misunderstanding and is not recommended](https://typescript-eslint.io/rules/no-empty-interface/). -
+```ts +interface AnyNonNullishValue {} // equivalent to `type AnyNonNullishValue = {}` or `type AnyNonNullishValue = Object` + +let value: AnyNonNullishValue; + +// these are all fine, but might not be expected +value = 1; +value = "foo"; +value = () => alert("foo"); +value = {}; +value = { foo: "bar" }; + +// these are errors +value = undefined; +value = null; +``` #### Useful React Prop Type Examples diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index 88f21f07..0a906e35 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -709,23 +709,63 @@ const UsageComponent = () => ( [View in the TypeScript Playground](https://www.typescriptlang.org/play/?jsx=2#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wFgAoCmATzCTgAUcwBnARjgF44BvOTCBABccFjCjAAdgHM4AXwDcVWvSYRWAJi684AIxRQRYiTPlLK5TAFdJGYBElwAstQDCuSJKSSYACjDMLCJqrBwAPoyBGgCUvBRwcMCYcL4ARAIQqYmOAeossTzxCXAA9CVwuawAdPpQpeVIUDhQRQlEMFZQjgA8ACbAAG4AfDyVLFUZct0l-cPmCXJwSAA2LPSF5MX1FYETgtuNza1w7Z09syNjNQZTM4ND8-IUchRoDmJwAKosKNJI7uAHN4YCJkOgYFUAGKubS+WKcIYpIp9e7HbouAGeYH8QScdKCLIlIZojEeIE+PQGPG1QnEzbFHglABUcHRbjJXgpGTxGSytWpBlSRO2UgGKGWwF6cCZJRe9OmFwo0QUQA) -Further reading: [how to ban passing `{}` if you have a `NoFields` type.](http://www.javiercasas.com/articles/typescript-impossible-states-irrepresentable) +## Props: Pass nothing or all -## Props: Must Pass Both +Passing no props is equivalent to passing an empty object. However, the type for an empty object is not `{}`, which you might think. [Make sure you understand what empty interface, `{}` and `Object` means](/docs/basic/getting-started/basic_type_example#empty-interface--and-object). `Record` is probably the closest you can get to an empty object type, and is [recommended by typescript-eslint](https://typescript-eslint.io/rules/ban-types/). Here's an example of allowing "nothing or all": ```tsx -type OneOrAnother = - | (T1 & { [K in keyof T2]?: undefined }) - | (T2 & { [K in keyof T1]?: undefined }); +type Nothing = Record; -type Props = OneOrAnother<{ a: string; b: string }, {}>; +interface All { + a: string; + b: string; +} + +const NothingOrAll = (props: Nothing | All) => { + if ("a" in props) { + return <>{props.b}; + } + return <>Nothing; +}; -const a: Props = { a: "a" }; // error -const b: Props = { b: "b" }; // error -const ab: Props = { a: "a", b: "b" }; // ok +const Component = () => ( + <> + {/* ok */} + {/* error */} + {/* error */} + {/* ok */} + +); +``` + +While this works, representing and empty object with `Record` [is not officially recommended](https://github.com/microsoft/TypeScript/issues/47486#issuecomment-1015671856). It might be better approaching this in another way, to avoid trying to type "an exactly empty object". One way is grouping the required props in an optional object: + +```tsx +interface Props { + obj?: { + a: string; + b: string; + }; +} + +const NothingOrAll = (props: Props) => { + if (props.obj) { + return <>{props.obj.a}; + } + return <>Nothing; +}; + +const Component = () => ( + <> + {/* ok */} + {/* error */} + {/* error */} + {/* ok */} + +); ``` -Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426) +Another way is to make both props optional and then check that either none or all props are passed at runtime. ## Props: Pass One ONLY IF the Other Is Passed diff --git a/docs/basic/getting-started/basic-type-examples.md b/docs/basic/getting-started/basic-type-examples.md index d8a12ba0..043a5a22 100644 --- a/docs/basic/getting-started/basic-type-examples.md +++ b/docs/basic/getting-started/basic-type-examples.md @@ -54,18 +54,32 @@ type AppProps = { }; ``` -Notice we have used the TSDoc `/** comment */` style here on each prop. You can and are encouraged to leave descriptive comments on reusable components. For a fuller example and discussion, see our [Commenting Components](https://react-typescript-cheatsheet.netlify.app/docs/advanced/misc_concerns/#commenting-components) section in the Advanced Cheatsheet. +### `object` as the non-primitive type -
-More on object types: object, {"{}"}, etc +`object` is a common source of misunderstanding in TypeScript. It does not mean "any object" but rather "any non-primitive type", which means it represents anything that is not `number`, `string`, `boolean`, `symbol`, `null` or `undefined`. -In Typescript, it's generally best to use specific types for objects. In most cases, this means a literal type like { id: string; name: string }. In cases where there isn't a fixed structure for an object, you likely either want an index signature (possibly with the Record shorthand) - if there are values of a certain type, but the keys can change - or else generics - if the object structure is more-or-less an arbitrary black-box. +Typing "any non-primitive value" is most likely not something that you should do much in React, which means you will probably not use `object` much. -Another approach to objects is the Map data structure, but this is somewhat uncommon to use in React, because React prefers data to be changed immutably (e.g. setUser({...user, name: newName})), while Maps are mutable data structures. +### Empty interface, `{}` and `Object` -"Vague" object types like object, {} are fairly niche and should be rarely used, and may function differently than you expect. object is any non-primitive value: this includes things like functions and arrays and constructors, not just "simple" objects. And {} is perhaps better thought of as "an interface with no required properties", not "an empty object" - in practice this type allows anything except null or undefined. Object behaves the same as {} and is basically never used. +An empty interface, `{}` and `Object` all represent "any non-nullish value"—not "an empty object" as you might think. [Using these types is a common source of misunderstanding and is not recommended](https://typescript-eslint.io/rules/no-empty-interface/). -
+```ts +interface AnyNonNullishValue {} // equivalent to `type AnyNonNullishValue = {}` or `type AnyNonNullishValue = Object` + +let value: AnyNonNullishValue; + +// these are all fine, but might not be expected +value = 1; +value = "foo"; +value = () => alert("foo"); +value = {}; +value = { foo: "bar" }; + +// these are errors +value = undefined; +value = null; +``` ## Useful React Prop Type Examples