Last active
January 27, 2023 14:51
-
-
Save itsjavi/ec5009b109f2722e6d20683e0a4dde0e to your computer and use it in GitHub Desktop.
TypeScript Polymorphic React Button Component using the "as" property
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
type PolymorphicPropsFactory<T, P> = { | |
as?: T | React.ElementType | |
children?: React.ReactNode | undefined | |
} & React.RefAttributes<T> & | |
P | |
type PolymorphicProps<T, FallbackPropsType> = | |
// as = Function Component | |
T extends React.FunctionComponent<infer P> | |
? PolymorphicPropsFactory<T, P> | |
: // as = Class Component | |
T extends React.ComponentClass<infer P> | |
? PolymorphicPropsFactory<T, P> | |
: // as = HTML Tag | |
T extends string | |
? PolymorphicPropsFactory<T, React.HTMLAttributes<T>> | |
: // ... fallback | |
PolymorphicPropsFactory<T, FallbackPropsType> | |
type BtnProps<T> = PolymorphicProps<T, React.ButtonHTMLAttributes<HTMLButtonElement>> | |
function Btn<T>({ as, ...rest }: BtnProps<T>) { | |
const BtnTag = as || 'button' | |
return <BtnTag {...rest} /> | |
} | |
type LinkProps = { | |
href: string | |
children?: React.ReactElement | any | |
} | |
const Link = ({ children, href, ...props }: LinkProps) => { | |
href = href as string | |
const isInternal = href.startsWith('/') || href.startsWith('#') | |
if (isInternal) { | |
return ( | |
<a href={href} {...props}> | |
{children} | |
</a> | |
) | |
} | |
return ( | |
<a href={href} {...props} target={'_blank'} rel={'noreferrer'}> | |
{children} | |
</a> | |
) | |
} | |
// playing around: | |
// TSC should complain about the "activeClass" property | |
const myBtns = ( | |
<> | |
<Btn as={Link} href="/login" activeClass={''}> | |
Btn rendered as the "Link" function component | |
</Btn> | |
<Btn as={'div'} onClick={() => false}> | |
Btn rendered as a "div" | |
</Btn> | |
<Btn onClick={() => false} type={'button'}> | |
Default Btn (HTML button element) | |
</Btn> | |
</> | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment