@@ -202,6 +202,121 @@ function RouterLink(props: LinkProps | AnchorProps) {
202
202
}
203
203
```
204
204
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
+
205
320
## Omit attribute from a type
206
321
207
322
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