|
17 | 17 | * [Higher Order Components](#higher-order-components-hocs)
|
18 | 18 | * [Render Props](#render-props)
|
19 | 19 | * [Types for Conditional Rendering](#types-for-conditional-rendering)
|
| 20 | + * [Props: One or the Other but not Both](#props-one-or-the-other-but-not-both) |
| 21 | + * [Props: Must Pass Both](#props-one-or-the-other-but-not-both) |
20 | 22 | * [Omit attribute from a type](#omit-attribute-from-a-type)
|
21 | 23 | * [Type Zoo](#type-zoo)
|
22 | 24 | * [Extracting Prop Types of a Component](#extracting-prop-types-of-a-component)
|
@@ -221,6 +223,51 @@ const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
|
221 | 223 | <AnchorButton href="/login" to="/test">Login</AnchorButton> // Error: Property 'to' does not exist on type...
|
222 | 224 | ```
|
223 | 225 |
|
| 226 | +## Props: One or the Other but not Both |
| 227 | + |
| 228 | +Use the `in` keyword, function overloading, and union types to make components that take either one or another sets of props, but not both: |
| 229 | + |
| 230 | +```tsx |
| 231 | +type Props1 = { foo: string } |
| 232 | +type Props2 = { bar: string } |
| 233 | + |
| 234 | +function MyComponent(props: Props1): JSX.Element |
| 235 | +function MyComponent(props: Props2): JSX.Element |
| 236 | +function MyComponent(props: Props1 | Props2) { |
| 237 | + if ('foo' in props) { |
| 238 | + // props.bar // error |
| 239 | + return <div>{props.foo}</div> |
| 240 | + } else { |
| 241 | + // props.foo // error |
| 242 | + return <div>{props.bar}</div> |
| 243 | + } |
| 244 | +} |
| 245 | +const UsageComponent: React.FC = () => ( |
| 246 | + <div> |
| 247 | + <MyComponent foo="foo" /> |
| 248 | + <MyComponent bar="bar" /> |
| 249 | + {/* <MyComponent foo="foo" bar="bar"/> // invalid */} |
| 250 | + </div> |
| 251 | +) |
| 252 | +``` |
| 253 | + |
| 254 | + |
| 255 | +## Props: Must Pass Both |
| 256 | + |
| 257 | +```tsx |
| 258 | +type OneOrAnother<T1, T2> = |
| 259 | + | (T1 & { [K in keyof T2]?: undefined }) |
| 260 | + | (T2 & { [K in keyof T1]?: undefined }) |
| 261 | + |
| 262 | +type Props = OneOrAnother<{ a: string; b: string }, {}> |
| 263 | + |
| 264 | +const a: Props = { a: 'a' } // error |
| 265 | +const b: Props = { b: 'b' } // error |
| 266 | +const ab: Props = { a: 'a', b: 'b' } // ok |
| 267 | +``` |
| 268 | + |
| 269 | +Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426) |
| 270 | + |
224 | 271 | ## Omit attribute from a type
|
225 | 272 |
|
226 | 273 | 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