Render either next/link or button in TypeScript
Unanswered
Burmese posted this in #help-forum
BurmeseOP
I have a
I'm getting three type errors with my current implementation:
1. For the props I'm getting a TS error that
2. The
3. The
How do people typically approach this, since this seems different and almost more complex than handling a polymorphic element using an
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} />
)
}