Skip to content

Commit f223a3f

Browse files
committed
Add prop example of prop A requiring prop B
Guides users on how to make a component where one prop is only allowed to be passed when another prop is passed.
1 parent 863aa3c commit f223a3f

File tree

1 file changed

+76
-1
lines changed

1 file changed

+76
-1
lines changed

Diff for: ADVANCED.md

+76-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
- [`as` props (passing a component to be rendered)](#as-props-passing-a-component-to-be-rendered)
4848
- [Types for Conditional Rendering](#types-for-conditional-rendering)
4949
- [Props: One or the Other but not Both](#props-one-or-the-other-but-not-both)
50-
- [Props: Must Pass Both](#props-one-or-the-other-but-not-both)
50+
- [Props: Can Optionally Pass One Only If the Other Is Passed](#props-can-optionally-pass-one-only-if-the-other-is-passed)
5151
- [Omit attribute from a type](#omit-attribute-from-a-type)
5252
- [Type Zoo](#type-zoo)
5353
- [Extracting Prop Types of a Component](#extracting-prop-types-of-a-component)
@@ -451,6 +451,81 @@ const ab: Props = { a: 'a', b: 'b' }; // ok
451451

452452
Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426)
453453

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

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