Skip to content

Commit a7fbdc6

Browse files
authored
Merge pull request #111 from stephenkoo/add-dependent-prop-example
Add dependent prop example
2 parents f443a18 + f546975 commit a7fbdc6

File tree

1 file changed

+77
-1
lines changed

1 file changed

+77
-1
lines changed

Diff for: ADVANCED.md

+77-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ The best tool for creating React + TS libraries right now is [`tsdx`](https://gi
5151
- [`as` props (passing a component to be rendered)](#as-props-passing-a-component-to-be-rendered)
5252
- [Types for Conditional Rendering](#types-for-conditional-rendering)
5353
- [Props: One or the Other but not Both](#props-one-or-the-other-but-not-both)
54-
- [Props: Must Pass Both](#props-one-or-the-other-but-not-both)
54+
- [Props: Must Pass Both](#props-must-pass-both)
55+
- [Props: Can Optionally Pass One Only If the Other Is Passed](#props-can-optionally-pass-one-only-if-the-other-is-passed)
5556
- [Omit attribute from a type](#omit-attribute-from-a-type)
5657
- [Type Zoo](#type-zoo)
5758
- [Extracting Prop Types of a Component](#extracting-prop-types-of-a-component)
@@ -455,6 +456,81 @@ const ab: Props = { a: 'a', b: 'b' }; // ok
455456

456457
Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426)
457458

459+
## Props: Can Optionally Pass One Only If the Other Is Passed
460+
461+
Say you want a Text component that gets truncated if `truncate` prop is passed but expands to show the full text when `expanded` prop is passed (e.g. when the user clicks the text).
462+
463+
You want to allow `expanded` to be passed only if `truncate` is also passed, because there is no use for `expanded` if the text is not truncated.
464+
465+
You can do this by function overloads:
466+
467+
```tsx
468+
import React from "react";
469+
470+
type CommonProps = {
471+
children: React.ReactNode;
472+
as: "p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
473+
};
474+
475+
type NoTruncateProps = CommonProps & {
476+
truncate?: false;
477+
};
478+
479+
type TruncateProps = CommonProps & {
480+
truncate: true;
481+
expanded?: boolean;
482+
};
483+
484+
// Type guard
485+
const isTruncateProps = (
486+
props: NoTruncateProps | TruncateProps
487+
): props is TruncateProps => !!props.truncate;
488+
489+
// Function overloads to accept both prop types NoTruncateProps & TruncateProps
490+
function Text(props: NoTruncateProps): JSX.Element;
491+
function Text(props: TruncateProps): JSX.Element;
492+
function Text(props: NoTruncateProps | TruncateProps) {
493+
494+
if (isTruncateProps(props)) {
495+
const { children, as: Tag, truncate, expanded, ...otherProps } = props;
496+
497+
const classNames = truncate ? ".truncate" : "";
498+
499+
return (
500+
<Tag
501+
className={classNames}
502+
aria-expanded={!!expanded}
503+
{...otherProps}
504+
>
505+
{children}
506+
</Tag>
507+
);
508+
}
509+
510+
const { children, as: Tag, ...otherProps } = props;
511+
512+
return <Tag {...otherProps}>{children}</Tag>;
513+
}
514+
515+
Text.defaultProps = {
516+
as: 'span'
517+
}
518+
```
519+
520+
Using the Text component:
521+
```tsx
522+
const App: React.FC = () => (
523+
<>
524+
<Text>not truncated</Text> {/* works */}
525+
<Text truncate>truncated</Text> {/* works */}
526+
<Text truncate expanded>truncate-able but expanded</Text> {/* works */}
527+
528+
{/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type 'Pick<TruncateProps, "expanded" | "children" | "truncate">'} */}
529+
<Text expanded>truncate-able but expanded</Text>
530+
</>
531+
);
532+
```
533+
458534
## Omit attribute from a type
459535

460536
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)