All Articles

Polymorphic Link/Button Components in React & TypeScript

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.
import React from "react";

type ButtonOrLinkProps =
  | React.ButtonHTMLAttributes<HTMLButtonElement>
  | React.AnchorHTMLAttributes<HTMLAnchorElement>;

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  if ("href" in props) {
    return <a {...props} />;
  }
  return <button {...props} />;
};
import React from "react";

type ButtonOrLinkProps =
  | React.ButtonHTMLAttributes<HTMLButtonElement>
  | React.AnchorHTMLAttributes<HTMLAnchorElement>;

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  if ("href" in props) {
    return <a {...props} />;
  }
  return <button {...props} />;
Type '{ disabled?: boolean | undefined; form?: string | undefined; formAction?: string | undefined; formEncType?: string | undefined; formMethod?: string | undefined; formNoValidate?: boolean | undefined; ... 266 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; } | { ...; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'. Type '{ download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; ... 264 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'. Type '{ download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; ... 264 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'ButtonHTMLAttributes<HTMLButtonElement>'. Types of property 'type' are incompatible. Type 'string | undefined' is not assignable to type '"button" | "submit" | "reset" | undefined'. Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.2322
Type '{ disabled?: boolean | undefined; form?: string | undefined; formAction?: string | undefined; formEncType?: string | undefined; formMethod?: string | undefined; formNoValidate?: boolean | undefined; ... 266 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; } | { ...; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'. Type '{ download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; ... 264 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'. Type '{ download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; ... 264 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'ButtonHTMLAttributes<HTMLButtonElement>'. Types of property 'type' are incompatible. Type 'string | undefined' is not assignable to type '"button" | "submit" | "reset" | undefined'. Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.};
import React from "react";

type ButtonOrLinkProps =
  | React.ButtonHTMLAttributes<HTMLButtonElement>
  | AnchorPropsWithRequiredHref;

type AnchorPropsWithRequiredHref =
  React.AnchorHTMLAttributes<HTMLAnchorElement> & {
    href: string;
  };

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  if ("href" in props) {
    return <a {...props} />;
  }
  return <button {...props} />;
};
<ButtonOrLink href="/" onClick={(e) => {}} />;
Parameter 'e' implicitly has an 'any' type.7006
Parameter 'e' implicitly has an 'any' type.
import React from "react";

type ButtonOrLinkProps =
  | (React.ButtonHTMLAttributes<HTMLButtonElement> & {
      as: "button";
    })
  | (React.AnchorHTMLAttributes<HTMLAnchorElement> & {
      as: "a";
    });

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  if (props.as === "a") {
    return <a {...props} />;
  }
  return <button {...props} />;
};
<ButtonOrLink
  as="a"
  href="/"
  onClick={(e) => {
    console.log(e);
(parameter) e: React.MouseEvent<HTMLAnchorElement, MouseEvent>
}} />; <ButtonOrLink as="button" onClick={(e) => { console.log(e);
(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>
}} />;
import React from "react";

type ButtonOrLinkProps =
  | (React.ButtonHTMLAttributes<HTMLButtonElement> & {
      as?: "button";
    })
  | (React.AnchorHTMLAttributes<HTMLAnchorElement> & {
      as: "a";
    });

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  if (props.as === "a") {
    return <a {...props} />;
  }
  return <button {...props} />;
};
<ButtonOrLink
  onClick={(e) => {
    console.log(e);
(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>
}} />; <ButtonOrLink as="a" onClick={(e) => { console.log(e);
(parameter) e: React.MouseEvent<HTMLAnchorElement, MouseEvent>
}} />;
Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock