Next.js Discord

Discord Forum

Render either next/link or button in TypeScript

Unanswered
Burmese posted this in #help-forum
Open in Discord
BurmeseOP
I have a Button component that, if passed an href, will render out a Link, otherwise it'll render a plain HTML button. What I'm trying to do is be able to just spread props on both and have it work, but I can't seem to make TS happy. I'm trying to use class-variance-authority to enforce some DLS standards, too, which might be adding to the complexity a bit.

I'm getting three type errors with my current implementation:

1. For the props I'm getting a TS error that className doesn't exist on type Props, which is weird, because it's an optional arg to cva.
2. The ButtonHTMLAttributes<HTMLButtonElement> is causing TS to complain when spreading on the Link because some of the button events don't exist on the Link or conflict with the Link ones.
3. The LinkProps is causing TS to complain when spreading on the <button> element, because as is not a valid prop, and other LinkProps aren't valid for a button element, either.

How do people typically approach this, since this seems different and almost more complex than handling a polymorphic element using an as prop?

1 Reply

BurmeseOP
Here's the code, for reference:

import Link, { LinkProps } from 'next/link'
import { cva, type VariantProps } from 'class-variance-authority'


const button = cva(['inline-flex', 'items-center', 'gap-2', 'justify-center', 'rounded-md', 'py-2', 'px-3', 'text-sm', 'outline-offset-2', 'transition', 'active:transition-none'], {
  variants: {
    intent: {
      primary: [
        'bg-zinc-800', 'font-semibold', 'text-zinc-100', 'hover:bg-zinc-700', 'active:bg-zinc-800', 'active:text-zinc-100/70', 'dark:bg-zinc-700', 'dark:hover:bg-zinc-600', 'dark:active:bg-zinc-700', 'dark:active:text-zinc-100/70'
      ],
      secondary: [
        'bg-zinc-50', 'font-medium', 'text-zinc-900', 'hover:bg-zinc-100', 'active:bg-zinc-100', 'active:text-zinc-900', 'dark:bg-zinc-800/50', 'dark:text-zinc-300', 'dark:hover:bg-zinc-800', 'dark:hover:text-zinc-50', 'dark:active:bg-zinc-800/50', 'dark:active:text-zinc-50/70'
      ]
    }
  },
  defaultVariants: {
    intent: 'primary'
  }
})



type Props = VariantProps<typeof button> & (LinkProps | React.ButtonHTMLAttributes<HTMLButtonElement>) 

export function Button({ intent, className, href, ...props }: Props) {
  const _className = button({intent, className})
  return href ? (
    <Link href={href} className={_className} {...props} /> 
  ) : (
    <button className={_className} {...props} />
  )
}