Skip to content

Commit 1c12b2b

Browse files
authored
retain some old info for people to grok
1 parent 6141fe0 commit 1c12b2b

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

ADVANCED.md

+115
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,121 @@ function RouterLink(props: LinkProps | AnchorProps) {
202202
}
203203
```
204204

205+
<details>
206+
<summary><b>Approach: Generic Components</b></summary>
207+
208+
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)*
209+
210+
```tsx
211+
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
212+
213+
interface LinkProps {}
214+
215+
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
216+
type RouterLinkProps = Omit<NavLinkProps, 'href'>
217+
218+
const Link = <T extends {}>(
219+
props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
220+
) => {
221+
if ((props as RouterLinkProps).to) {
222+
return <NavLink {...props as RouterLinkProps} />
223+
} else {
224+
return <a {...props as AnchorProps} />
225+
}
226+
}
227+
228+
<Link<RouterLinkProps> to="/">My link</Link> // ok
229+
<Link<AnchorProps> href="/">My link</Link> // ok
230+
<Link<RouterLinkProps> to="/" href="/">My link</Link> // error
231+
```
232+
233+
</details>
234+
235+
236+
237+
<details>
238+
<summary><b>Approach: Composition</b></summary>
239+
240+
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:
241+
242+
```tsx
243+
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
244+
245+
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
246+
type RouterLinkProps = Omit<NavLinkProps, 'href'>
247+
248+
interface Button {
249+
as: React.ComponentClass | 'a'
250+
}
251+
252+
const Button: React.FunctionComponent<Button> = (props) => {
253+
const {as: Component, children, ...rest} = props
254+
return (
255+
<Component className="button" {...rest}>{children}</Component>
256+
)
257+
}
258+
259+
const AnchorButton: React.FunctionComponent<AnchorProps> = (props) => (
260+
<Button as="a" {...props} />
261+
)
262+
263+
const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
264+
<Button as={NavLink} {...props} />
265+
)
266+
267+
<LinkButton to="/login">Login</LinkButton>
268+
<AnchorButton href="/login">Login</AnchorButton>
269+
<AnchorButton href="/login" to="/test">Login</AnchorButton> // Error: Property 'to' does not exist on type...
270+
```
271+
</details>
272+
273+
274+
275+
## Props: One or the Other but not Both
276+
277+
Use the `in` keyword, function overloading, and union types to make components that take either one or another sets of props, but not both:
278+
279+
```tsx
280+
type Props1 = { foo: string }
281+
type Props2 = { bar: string }
282+
283+
function MyComponent(props: Props1): JSX.Element
284+
function MyComponent(props: Props2): JSX.Element
285+
function MyComponent(props: Props1 | Props2) {
286+
if ('foo' in props) {
287+
// props.bar // error
288+
return <div>{props.foo}</div>
289+
} else {
290+
// props.foo // error
291+
return <div>{props.bar}</div>
292+
}
293+
}
294+
const UsageComponent: React.FC = () => (
295+
<div>
296+
<MyComponent foo="foo" />
297+
<MyComponent bar="bar" />
298+
{/* <MyComponent foo="foo" bar="bar"/> // invalid */}
299+
</div>
300+
)
301+
```
302+
303+
304+
## Props: Must Pass Both
305+
306+
```tsx
307+
type OneOrAnother<T1, T2> =
308+
| (T1 & { [K in keyof T2]?: undefined })
309+
| (T2 & { [K in keyof T1]?: undefined })
310+
311+
type Props = OneOrAnother<{ a: string; b: string }, {}>
312+
313+
const a: Props = { a: 'a' } // error
314+
const b: Props = { b: 'b' } // error
315+
const ab: Props = { a: 'a', b: 'b' } // ok
316+
```
317+
318+
Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426)
319+
205320
## Omit attribute from a type
206321

207322
Sometimes when intersecting types, we want to define our own version of an attribute. For example, I want my component to have a `label`, but the type I am intersecting with also has a `label` attribute. Here's how to extract that out:

0 commit comments

Comments
 (0)