1

I've been digging around SO and the web at large for a solution, but I can't seem to nail it.

I have two components, Link and Button. Long story short: they are wrappers for <a> and <button> elements, but with the added options such as chevrons on the right-side, icons on the left-side, full-width mode, etc.

Here is what I have so far (and here's the code running on typescriptlang.org/play):

type Variant = "primary" | "secondary" | "tertiary";

interface CommonProps {
  variant?: Variant;
  showChevron?: boolean;
  icon?: IconDefinition;
  fullWidth?: boolean;
  small?: boolean;
}

interface LinkOnlyProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  href: string;
}

interface ButtonOnlyProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  onClick: React.MouseEventHandler<HTMLButtonElement>;
}

export type LinkProps = CommonProps & LinkOnlyProps;
export type ButtonProps = CommonProps & ButtonOnlyProps;

export const Link = (props: LinkProps) => {
  const {
    children,
    showChevron,
    icon,
    fullWidth,
    variant,
    small,
    className,
    ...rest
  } = props;
  const { classes } = initButton(props);
  return (
    <a className={classes} {...rest}>
      <Content {...props} />
    </a>
  );
};

export const Button = (props: ButtonProps) => {
  const {
    children,
    showChevron,
    icon,
    fullWidth,
    variant,
    small,
    className,
    ...rest
  } = props;
  const { classes } = initButton(props);
  return (
    <button className={classes} {...rest}>
      <Content {...props} />
    </button>
  );
};

I've tried extracting the common logic for the Link and Button components into a single Component, however when I spread the ...rest props I get TypeScript yelling at me. From the error, it seems because I haven't been able to account for the possibility of <a> props being spread on to a <button> element and vice-versa.

I wanted to keep Link and Button as separate components, rather than specifying the type as a prop, so that the intentionality of the developer is clear when the components are being implemented.

Is there any possibility of extracting that common logic into a central component that both Link and Button can simply act as wrappers for? For example:

export const Link = (props: LinkProps) => {
  return <Component element="a" {...props} />;
}
export const Button = (props: ButtonProps) => {
  return <Component element="button" {...props} />;
}
Dan Walsh
  • 11
  • 5

1 Answers1

0

Was able to work around the type assertion using as any when spreading the rest of my props:

return (
  <Element className={classes} {...(rest as any)}>
    <Content {...props} />
  </Element>
);
Dan Walsh
  • 11
  • 5