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

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock