Skip to content

Commit 70b0f7d

Browse files
authored
Merge branch 'master' into MIGRATING
2 parents 42b3c4a + d7ddac6 commit 70b0f7d

File tree

2 files changed

+69
-24
lines changed

2 files changed

+69
-24
lines changed

ADVANCED.md

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,52 @@ export interface Props {
180180
[Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new).
181181
182182
183-
## Types for Conditional Rendering
183+
## Typing a Component that Accepts Different Props
184184
185-
Components can render different things based on props that are passed in, and this can be confusing to model in terms of argument and return types. See the Type checks, guards, and assertion strategies discussed above as a first resort.
185+
Components, and JSX in general, are analogous to functions. When a component can render differently based on their props, it's similar to how a function can be overloaded to have multiple call signatures. In the same way, you can overload a function component's call signature to list all of its different "versions".
186186
187-
You can also do fairly advanced logic within your types ([they are Turing complete!](https://github.com/Microsoft/TypeScript/issues/14833)). Read the [Advanced Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html) section of the docs for ideas on how to use `Pick`, `ReadOnly`, `Partial`, and `Record`. Here is an example solution, see the further discussion for other solutions. *thanks to [@jpavon](https://github.com/sw-yx/react-typescript-cheatsheet/issues/12#issuecomment-394440577)*
187+
A very common use case for this is to render something as either a button or an anchor, based on if it receives a `href` attribute.
188+
```tsx
189+
type ButtonProps = JSX.IntrinsicElements['button']
190+
type AnchorProps = JSX.IntrinsicElements['a']
191+
192+
// optionally use a custom type guard
193+
function isPropsForAnchorElement(props: ButtonProps | AnchorProps): props is AnchorProps {
194+
return 'href' in props
195+
}
188196

197+
function Clickable(props: ButtonProps): JSX.Element
198+
function Clickable(props: AnchorProps): JSX.Element
199+
function Clickable(props: ButtonProps | AnchorProps) {
200+
if (isPropsForAnchorElement(props)) {
201+
return <a {...props} />
202+
} else {
203+
return <button {...props } />
204+
}
205+
}
206+
```
189207

208+
They don't even need to be completely different props, as long as they have at least one difference in properties:
209+
```tsx
210+
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
211+
type LinkProps = Omit<JSX.IntrinsicElements[ 'a' ], 'href'> & { to?: string }
212+
213+
function RouterLink(props: LinkProps): JSX.Element
214+
function RouterLink(props: AnchorProps): JSX.Element
215+
function RouterLink(props: LinkProps | AnchorProps) {
216+
if ('to' in props) {
217+
return <a {...props} />
218+
} else {
219+
return <Link {...props } />
220+
}
221+
}
222+
```
223+
224+
<details>
225+
<summary><b>Approach: Generic Components</b></summary>
226+
227+
Here is an example solution, see the further discussion for other solutions. *thanks to [@jpavon](https://github.com/sw-yx/react-typescript-cheatsheet/issues/12#issuecomment-394440577)*
228+
190229
```tsx
191230
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
192231
@@ -209,8 +248,15 @@ const Link = <T extends {}>(
209248
<Link<AnchorProps> href="/">My link</Link> // ok
210249
<Link<RouterLinkProps> to="/" href="/">My link</Link> // error
211250
```
251+
252+
</details>
253+
212254

213-
If you want to conditionaly render a component, sometimes is better to use [React's composition model](https://reactjs.org/docs/composition-vs-inheritance.html) to have simpler components and better to understand typings:
255+
256+
<details>
257+
<summary><b>Approach: Composition</b></summary>
258+
259+
If you want to conditionally render a component, sometimes is better to use [React's composition model](https://reactjs.org/docs/composition-vs-inheritance.html) to have simpler components and better to understand typings:
214260

215261
```tsx
216262
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
@@ -241,6 +287,9 @@ const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
241287
<AnchorButton href="/login">Login</AnchorButton>
242288
<AnchorButton href="/login" to="/test">Login</AnchorButton> // Error: Property 'to' does not exist on type...
243289
```
290+
</details>
291+
292+
244293

245294
## Props: One or the Other but not Both
246295

@@ -332,20 +381,10 @@ The advantage of extracting the prop types is that you won't need to export ever
332381
333382
334383
```ts
335-
// helper type for all known valid JSX element constructors (class and function based)
336-
type ElementConstructor<P> =
337-
| ((props: P) => React.ReactElement<any> | null)
338-
| (new (props: P) => React.Component<P, any, any>);
339-
340-
// gets the internal props of a component
341-
// used like Props<typeof MyComponent>
342-
// or Props<'button'> for intrinsic HTML attributes
343-
type Props<C> = C extends ElementConstructor<infer P>
344-
? P
345-
: C extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[C] : {};
384+
import { ComponentProps, JSXElementConstructor } from 'react'
346385

347386
// goes one step further and resolves with propTypes and defaultProps properties
348-
type ApparentProps<C> = C extends ElementConstructor<infer P> ? JSX.LibraryManagedAttributes<C, P> : Props<C>
387+
type ApparentComponentProps<C> = C extends JSXElementConstructor<infer P> ? JSX.LibraryManagedAttributes<C, P> : ComponentProps<C>
349388
```
350389
351390
You can also use them to strongly type custom event handlers if they're not written at the call sites themselves

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Translations:
6262
- [Troubleshooting Handbook: tsconfig.json](#troubleshooting-handbook-tsconfigjson)
6363
- [Recommended React + TypeScript codebases to learn from](#recommended-react--typescript-codebases-to-learn-from)
6464
- [Recommended React + TypeScript talks](#recommended-react--typescript-talks)
65+
- [Editor Tooling and Integration](#editor-tooling-and-integration)
6566
- [Other React + TypeScript resources](#other-react--typescript-resources)
6667
- [Time to Really Learn TypeScript](#time-to-really-learn-typescript)
6768
</details>
@@ -984,14 +985,6 @@ const f = (e: PlotlyHTMLElement) => {
984985
};
985986
```
986987

987-
<details>
988-
989-
<summary>Explanation</summary>
990-
991-
This is not yet written. Please PR or [File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new) with your suggestions!
992-
993-
</details>
994-
995988
# Recommended React + TypeScript codebases to learn from
996989

997990
- https://github.com/jaredpalmer/formik
@@ -1016,6 +1009,19 @@ React Native Boilerplates: _contributed by [@spoeck](https://github.com/sw-yx/re
10161009
- https://github.com/emin93/react-native-template-typescript
10171010
- <https://github.com/Microsoft/TypeScript-React-Native-Starter>
10181011

1012+
# Editor Tooling and Integration
1013+
1014+
- VSCode
1015+
- swyx's VSCode Extension: https://github.com/sw-yx/swyx-react-typescript-snippets
1016+
- amVim: https://marketplace.visualstudio.com/items?itemName=auiworks.amvim
1017+
- VIM
1018+
- https://github.com/Quramy/tsuquyomi
1019+
- nvim-typescript?
1020+
- https://github.com/leafgarland/typescript-vim
1021+
- peitalin/vim-jsx-typescript
1022+
- NeoVim: https://github.com/neoclide/coc.nvim
1023+
- other discussion: https://mobile.twitter.com/ryanflorence/status/1085715595994095620
1024+
10191025
# Other React + TypeScript resources
10201026

10211027
- me! <https://twitter.com/swyx>

0 commit comments

Comments
 (0)