0
votes

For a button link component, I'm trying to choose between HTMLButtonAttributes or HTMLAnchorAttributes based on passed props. My knowledge of TypeScript is quite limited.

I have the following types and interfaces:

interface CommonButtonProps {
   // custom props
}

export interface ButtonProps extends CommonButtonProps,
    React.ButtonHTMLAttributes<HTMLButtonElement> {}

export interface ButtonLinkProps extends CommonButtonProps,
    React.AnchorHTMLAttributes<HTMLAnchorElement> {
  href: string;
}

export type ButtonOrButtonLinkProps = ButtonProps | ButtonLinkProps;

export const isButtonLink = (props: ButtonOrButtonLinkProps,): props is ButtonLinkProps => guard logic

And the following Button component with ButtonOrButtonLinkProps:

export const Button: React.FC<ButtonOrButtonLinkProps> = props => {
  const buttonLink = isButtonLink(props);

  const commonProps = {
  };

  const linkProps: ButtonLinkProps = {
    ...commonProps,
    onClick: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
      // Do stuff
      if (props.onClick) {
        props.onClick(event); // Error
      }
    },
  };


  const buttonProps: ButtonProps = {
    ...commonProps,
    type: 'button',
    onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
      // Do other stuff
      if (props.onClick) {
        props.onClick(event); // Error
      }
    },
  };

  return (
    <StyledBtn {...(buttonLink ? linkProps : buttonProps)}>
      {children}
    </StyledBtn>
  );
};

The following line throws an error:

  if (props.onClick) {
    props.onClick(event); // Error
  }

Argument of type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to parameter of type 'MouseEvent<HTMLButtonElement, MouseEvent> & MouseEvent<HTMLAnchorElement, MouseEvent>'. Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type 'MouseEvent<HTMLAnchorElement, MouseEvent>'. Type 'HTMLButtonElement' is missing the following properties from type 'HTMLAnchorElement': charset, coords, download, hreflang, and 19 more.

It seems the current type of onClick is enter image description here

Why is the event type and intersection of MouseEvent of HTMLButtonElement & MouseEvent of HTMLAnchorElement ? I kinda expected it to be an union - either one or the other ?

Any idea how to fix the issue ?

1
please provide your code structure in codesandboxTemoncher

1 Answers

0
votes

Probably you shouldn't do it this way

Your component's props is public API of isolated code block, so you don't want to expand it too far. The best solution would be not to include all of link/button props into Button props, but include only necessary ones

But if you have to

The problem is that typescript does not understand what onClick type is, because it can be different based on props you pass. By the time typescript comes to props.onClick(execution) there are still ambiguity in type of props.

Solution

Use your typeguard to let typescript know what type of props is

export const Button: React.FC<...> = props => {
  // props is of type `ButtonProps | ButtonLinkProps` here
  // so `onClick` is ambiguous too

  if (isButtonLink(props)) {
    // Do button link stuff
    // props is of type `ButtonLinkProps` here
  } else {
    // Do button stuff
    // props is of type `ButtonProps` here
  }

  // props is again of type `ButtonProps | ButtonLinkProps` here
  
  return (...);
}

Playground link on typeguards