Skip to content

Replace invalid props example #619

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 3 commits into from
Apr 12, 2023
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
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<details>
<summary>More on object types: <code>object</code>, <code>{"{}"}</code>, etc</summary>
`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 <code>{ id: string; name: string }</code>. In cases where there isn't a fixed structure for an object, you likely either want an index signature (possibly with the <code>Record</code> shorthand) - if there are values of a certain type, but the keys can change - or else <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">generics</a> - 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 <code>Map</code> data structure, but this is somewhat uncommon to use in React, because React prefers data to be changed immutably (e.g. <code>setUser({...user, name: newName})</code>), while Maps are mutable data structures.
##### Empty interface, `{}` and `Object`

"Vague" object types like <code>object</code>, <code>{}</code> are fairly niche and should be rarely used, and may function differently than you expect. <code>object</code> is any non-primitive value: this includes things like functions and arrays and constructors, not just "simple" objects. And <code>{}</code> is perhaps better thought of as "an interface with no required properties", not "an empty object" - in practice this type allows anything except <code>null</code> or <code>undefined</code>. <code>Object</code> behaves the same as <code>{}</code> 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/).

</details>
```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

Expand Down
60 changes: 50 additions & 10 deletions docs/advanced/patterns_by_usecase.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, never>` 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, T2> =
| (T1 & { [K in keyof T2]?: undefined })
| (T2 & { [K in keyof T1]?: undefined });
type Nothing = Record<string, never>;

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 = () => (
<>
<NothingOrAll /> {/* ok */}
<NothingOrAll a="" /> {/* error */}
<NothingOrAll b="" /> {/* error */}
<NothingOrAll a="" b="" /> {/* ok */}
</>
);
```

While this works, representing and empty object with `Record<string, never>` [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 = () => (
<>
<NothingOrAll /> {/* ok */}
<NothingOrAll obj={{ a: "" }} /> {/* error */}
<NothingOrAll obj={{ b: "" }} /> {/* error */}
<NothingOrAll obj={{ a: "", b: "" }} /> {/* 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

Expand Down
28 changes: 21 additions & 7 deletions docs/basic/getting-started/basic-type-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<details>
<summary>More on object types: <code>object</code>, <code>{"{}"}</code>, etc</summary>
`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 <code>{ id: string; name: string }</code>. In cases where there isn't a fixed structure for an object, you likely either want an index signature (possibly with the <code>Record</code> shorthand) - if there are values of a certain type, but the keys can change - or else <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">generics</a> - 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 <code>Map</code> data structure, but this is somewhat uncommon to use in React, because React prefers data to be changed immutably (e.g. <code>setUser({...user, name: newName})</code>), while Maps are mutable data structures.
### Empty interface, `{}` and `Object`

"Vague" object types like <code>object</code>, <code>{}</code> are fairly niche and should be rarely used, and may function differently than you expect. <code>object</code> is any non-primitive value: this includes things like functions and arrays and constructors, not just "simple" objects. And <code>{}</code> is perhaps better thought of as "an interface with no required properties", not "an empty object" - in practice this type allows anything except <code>null</code> or <code>undefined</code>. <code>Object</code> behaves the same as <code>{}</code> 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/).

</details>
```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

Expand Down