From f223a3f189b548e5ca7d5f455961f558c7b607e1 Mon Sep 17 00:00:00 2001 From: Stephen Koo Date: Tue, 30 Apr 2019 10:41:11 +1000 Subject: [PATCH 1/2] 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. --- ADVANCED.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/ADVANCED.md b/ADVANCED.md index 717c13a7..8705c104 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -47,7 +47,7 @@ - [`as` props (passing a component to be rendered)](#as-props-passing-a-component-to-be-rendered) - [Types for Conditional Rendering](#types-for-conditional-rendering) - [Props: One or the Other but not Both](#props-one-or-the-other-but-not-both) - - [Props: Must Pass Both](#props-one-or-the-other-but-not-both) + - [Props: Can Optionally Pass One Only If the Other Is Passed](#props-can-optionally-pass-one-only-if-the-other-is-passed) - [Omit attribute from a type](#omit-attribute-from-a-type) - [Type Zoo](#type-zoo) - [Extracting Prop Types of a Component](#extracting-prop-types-of-a-component) @@ -451,6 +451,81 @@ const ab: Props = { a: 'a', b: 'b' }; // ok Thanks [diegohaz](https://twitter.com/kentcdodds/status/1085655423611367426) +## Props: Can Optionally Pass One Only If the Other Is Passed + +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). + +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. + +You can do this by function overloads: + +```tsx +import React from "react"; + +type CommonProps = { + children: React.ReactNode; + as: "p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; +}; + +type NoTruncateProps = CommonProps & { + truncate?: false; +}; + +type TruncateProps = CommonProps & { + truncate: true; + expanded?: boolean; +}; + +// Type guard +const isTruncateProps = ( + props: NoTruncateProps | TruncateProps +): props is TruncateProps => !!props.truncate; + +// Function overloads to accept both prop types NoTruncateProps & TruncateProps +function Text(props: NoTruncateProps): JSX.Element; +function Text(props: TruncateProps): JSX.Element; +function Text(props: NoTruncateProps | TruncateProps) { + + if (isTruncateProps(props)) { + const { children, as: Tag, truncate, expanded, ...otherProps } = props; + + const classNames = truncate ? ".truncate" : ""; + + return ( + + {children} + + ); + } + + const { children, as: Tag, ...otherProps } = props; + + return {children}; +} + +Text.defaultProps = { + as: 'span' +} +``` + +Using the Text component: +```tsx +const App: React.FC = () => ( + <> + not truncated {/* works */} + truncated {/* works */} + truncate-able but expanded {/* works */} + + {/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type 'Pick'} */} + truncate-able but expanded + +); +``` + ## Omit attribute from a type 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: From f5469758cc844eef664d38af4edece3fdd8f4e89 Mon Sep 17 00:00:00 2001 From: Stephen Koo Date: Tue, 30 Apr 2019 10:41:21 +1000 Subject: [PATCH 2/2] Fix incorrect relative link --- ADVANCED.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADVANCED.md b/ADVANCED.md index 8705c104..d5f0b118 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -47,6 +47,7 @@ - [`as` props (passing a component to be rendered)](#as-props-passing-a-component-to-be-rendered) - [Types for Conditional Rendering](#types-for-conditional-rendering) - [Props: One or the Other but not Both](#props-one-or-the-other-but-not-both) + - [Props: Must Pass Both](#props-must-pass-both) - [Props: Can Optionally Pass One Only If the Other Is Passed](#props-can-optionally-pass-one-only-if-the-other-is-passed) - [Omit attribute from a type](#omit-attribute-from-a-type) - [Type Zoo](#type-zoo)