👋

🤔

🤔

  • Huge API

🤔

  • Huge API
  • Outdated docs

🤔

  • Huge API
  • Outdated docs
  • Hard to test

🤔

  • Huge API
  • Outdated docs
  • Hard to test
  • Impossible to refactor

🤔

  • Huge API
  • Outdated docs
  • Hard to test
  • Impossible to refactor
  • Bad user experience

🙃

🙃

👋

But how?

But how?

  • Explicit and extendable props
  • Easily accessible documentation
  • Trust your components
  • Optimise for change

Props

Props

=
API

API

Design

API

Design

  • Versioning
  • Error messages
  • Developer Surprise
  • Minimize your surface

Avoid boolean props

Tip # 1

Avoid boolean props

Tip # 1

<Button primary>
  Buy more stuff
</Button>

Avoid boolean props

Tip # 1

<Button secondary>
  Buy more stuff
</Button>

Avoid boolean props

Tip # 1

<Button cta>
  Buy more stuff
</Button>

Avoid boolean props

Tip # 1

<Button 
  cta 
  primary 
  secondary
>
  Buy more stuff
</Button>

Avoid boolean props

Tip # 1

<Button 
  variant="cta"
>
  Buy more stuff
</Button>

Avoid boolean props

Tip # 1

<Button 
  variant="cta"
  loading
  disabled
>
  Buying more stuff
</Button>

Allow for composability

Tip # 2

Allow for composability

Tip # 2

<Modal />

Allow for composability

Tip # 2

<Modal 
  showCloseButton
  isOpen
  title="My modal"
  content="Here is some modal content"
  onLeftButtonClick={handleLeftButtonClick}
  onRightButtonClick={handleRightButtonClick}
  leftButtonLabel="Cancel"
  rightButtonLabel="Ok"
  rightButtonVariant="secondary"
  overlayColor="rgba(0,0,255,1)"
  background="white"
/>

Allow for composability

Tip # 2

<ModalOverlay>
  Are you sure you want a modal?
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    Are you sure you want a modal?
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalCloseButton />
    Are you sure you want a modal?
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalCloseButton />
    <ModalTitle>Modals!</ModalTitle>
    Are you sure you want a modal?
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalCloseButton />
    <ModalTitle>Modals!</ModalTitle>
    <ModalContent>
      Are you sure you want a modal?
    </ModalContent>
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalCloseButton />
    <ModalTitle>Modals!</ModalTitle>
    <ModalContent>
      Are you sure you want a modal?
    </ModalContent>
    <ModalFooter>
      <button>
        Nah
      </button>
    </ModalFooter>
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalCloseButton />
    <ModalTitle>Modals!</ModalTitle>
    <ModalContent>
      Are you sure you want a modal?
    </ModalContent>
    <ModalFooter>
      <button>
        Nah
      </button>
    </ModalFooter>
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

<ModalOverlay>
  <ModalBox>
    <ModalContent>
      Are you sure you want a modal?
    </ModalContent>
    <ModalFooter>
      <button>
        Nah
      </button>
    </ModalFooter>
  </ModalBox>
</ModalOverlay>

Allow for composability

Tip # 2

const Modal = (props) => (
  <ModalOverlay>
    <ModalBox>
      <ModalCloseButton />
      <ModalTitle>{props.title}</ModalTitle>
      <ModalContent>
        {props.children}
      </ModalContent>
      <ModalFooter>
        {props.buttons}
      </ModalFooter>
    </ModalBox>
  </ModalOverlay>
);

Allow for composability

Tip # 2

<Modal title="Modals are cool">
  And composition is the best!
</Modal>

Allow for semantics

Tip # 3

Allow for semantics

Tip # 3

<Card>
  <CardImage src="/phone.png" alt="My phone" />
  <CardContent>
    My new phone
  </CardContent>
</Card>

Allow for semantics

Tip # 3

<Card as="li">
  <CardImage src="/phone.png" alt="My phone" />
  <CardContent>
    My new phone
  </CardContent>
</Card>

Allow for semantics

Tip # 3

<Card as={Link} to="/phone">
  <CardImage src="/phone.png" alt="My phone" />
  <CardContent>
    My new phone
  </CardContent>
</Card>

Allow for semantics

Tip # 3

function Card(props) {
  return (
    <div className="card">
      {props.children}
    </div>
  );
}

Allow for semantics

Tip # 3

function Card(props) {
  const Element = props.as ?? 'div';
  return (
    <Element className="card">
      {props.children}
    </Element>
  );
}

Allow for semantics

Tip # 3

function Card({ children, as: Element = 'div' }) {
  return (
    <Element className="card">
      {props.children}
    </Element>
  );
}

Allow for semantics

Tip # 3

  • <Card as="li" />
  • <Button as="a" />
  • <GridRow as="section" />
  • <Box as="footer" />

Spread the remaining props

Tip # 4

Spread the remaining props

Tip # 4

function Card({ 
  children, 
  as: Element = 'div',
}) {
  return (
    <Element className="card">
      {props.children}
    </Element>
  );
}

Spread the remaining props

Tip # 4

function Card({ 
  children, 
  as: Element = 'div',
  ...rest
}) {
  return (
    <Element className="card">
      {props.children}
    </Element>
  );
}

Spread the remaining props

Tip # 4

function Card({ 
  children, 
  as: Element = 'div',
  ...rest
}) {
  return (
    <Element className="card" {...rest}>
      {props.children}
    </Element>
  );
}

Spread the remaining props

Tip # 4

function Card({ 
  children, 
  className = '',
  as: Element = 'div',
  ...rest
}) {
  return (
    <Element 
      className={`card ${className}`} 
      {...rest}
    >
      {props.children}
    </Element>
  );
}

Spread the remaining props

Tip # 4

<Card 
  as={Link}
  to="/phone"
  className="extra-classy"
>
  <CardImage src="/phone.png" alt="My phone" />
  <CardContent>
    My phone
  </CardContent>
</Card>

Spread the remaining props

Tip # 4

  • Combine props with internal props
  • Override or be overridden
  • Works best on single node components

Avoid renaming existing DOM props

Tip # 5

Avoid renaming existing DOM props

Tip # 5

<Button />

Avoid renaming existing DOM props

Tip # 5

<Button type="primary" />

Avoid renaming existing DOM props

Tip # 5

<Button type="primary" buttonType="button" />

Avoid renaming existing DOM props

Tip # 5

<Button type="button" variant="primary" />

Avoid renaming existing DOM props

Tip # 5

<Button 
  type="button" 
  variant="primary" 
/>

Avoid renaming existing DOM props

Tip # 5

<Button 
  type="button" 
  variant="primary" 
  isLoading
/>

Avoid renaming existing DOM props

Tip # 5

<Button 
  type="button" 
  variant="primary" 
  ariaBusy
/>

Avoid renaming existing DOM props

Tip # 5

<Button 
  type="button" 
  variant="primary" 
  aria-busy
/>

Avoid renaming existing DOM props

Tip # 5

<Button 
  type="button" 
  variant="primary" 
  aria-busy
  aria-label="Please wait"
/>

Design your API

Design your props

Document your props

⬅ Your docs

  • Tedious
  • Waste of time
  • Outdated

😠

  • Easily available
  • Always up to date
  • Makes it easier to understand code

😍

JSDoc

Tip # 1

JSDoc

Tip # 1

JSDoc

Tip # 1

/** Card
 *
 * Used for creating elements that pop out from the page
 * Can also be used as clickable elements, with or without
 * images, titles and text content.
 * 
 * If you need more control, please look at the composable
 * components instead - CardBox, CardContent, CardTitle and CardImage
 */
function Card({ 
  children, 
  className = '',
  as: Element = 'div',
  ...rest
}) {
  return ...;
}

JSDoc

Tip # 1

/** Card
 *
 * Used for creating elements that pop out from the page
 * Can also be used as clickable elements, with or without
 * images, titles and text content.
 * 
 * If you need more control, please look at the composable
 * components instead - CardBox, CardContent, CardTitle and CardImage
 *  
 * @example
 * <Card as="a" href="/example">
 *   Go to example
 * </Card>
 */
function Card({ 
  children, 
  className = '',
  as: Element = 'div',
  ...rest
}) {
  return ...;
}

JSDoc

Tip # 1

/** Card
 *
 * ...
 */
function Card({
  /** The content inside the card. Could be just text, or a CardImage component */
  children, 
  /** Extra class names are added to the root node */
  className = '',
  /** The type of the root element. Defaults to div. */
  as: Element = 'div',
  ...rest
}) {
  return ...;
}

JSDoc

Tip # 1

Dev-only warnings

Tip # 3

Dev-only warnings

Tip # 3

  • Helps newcomers use your code right
  • Warnings live with your code
  • Tree-shakes out!

Document your props

Trust your components

Trust your components

  • Confident they work
  • Confident they can be changed

Test your components

Let static analysis save the day

Tip # 1

Let static analysis save the day

Tip # 1

  • Infinite loops
  • Missing dependencies
  • Borked accessibility
  • Code smells

Let static analysis save the day

Tip # 1

// eslintrc.json
{
  "extends": "react-app"
}

Let type systems
save the day

Tip # 2

Let type systems
save the day

Tip # 2

type ButtonProps = {
  as?: string | React.ComponentType;
  variant: 'primary' | 'secondary' | 'cta';
  onClick: () => void;
  children: React.ReactNode;
}

const Button: React.FC<ButtonProps> = (props) => {
  return (...);
}

Let type systems
save the day

Tip # 2

  • easier to discover APIs
  • replaces your unit tests
  • improves refactoring

Avoid testing implementation details

Tip # 3

Avoid testing implementation details

Tip # 3

  • Use @testing-library/react
  • Test components via their props
  • Assert on the output

Broad strokes

Tip # 4

  • Great props APIs
  • Document the right way
  • Use static analysis
  • Wide reaching tests
Made with Slides.com