Type-safe polymorphic React components
A polymorphic component is a design pattern that allows you to customize the HTML tag used by a React component via props:
<Typography as='label'>foo</Typography>
Typography
is polymorphic because the generated HTML will be an actual HTML
label
tag:
<label>foo</label>
This is easy to do if we’re just using JavaScript and do not care about type-safety:
const Typographyconst Typography: ({ as: Component }: {
as?: string | undefined;
}) => React.JSX.Element
= ({ asas?: string | undefined
: Componenttype Component: string
= 'span' }) => {
return <Componenttype Component: string
/>
}
It gets at lot more challenging if we want the component to be strongly-typed.
For instance, say we don’t want to allow the consumer to pass props that
doesn’t make sense for a specific HTML element — a span
HTML element is not
supposed to be passed a for
attribute:
// This should be fine
<Typography as="label" htmlFor='' />
// @ts-expect-error This shouldn't: htmlFor is not allowed in span elements
<Typography as="span" htmlFor="" />
So let’s start typing this component.
Type-safe as
prop
The as
prop should only ever receive HTML tags like span
, div
etc.
Arbitrary string like foo
should break the build:
<Typography as='foo' />
What should be a type that accepts only valid HTML tags? Fortunately
@types/react
already provides us with such a type: React.ElementType
:
type TypographyPropstype TypographyProps = {
as?: React.ElementType;
}
= {
asas?: React.ElementType<any> | undefined
?: React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
}
const Typographyconst Typography: ({ as: Component, ...rest }: TypographyProps) => React.JSX.Element
= ({ asas?: React.ElementType<any> | undefined
: Componenttype Component: React.ElementType<any>
= 'span', ...restrest: {}
}: TypographyPropstype TypographyProps = {
as?: React.ElementType;
}
) => {
return <Componenttype Component: React.ElementType<any>
{...restrest: {}
} />
}
export default function Appfunction App(): React.JSX.Element
() {
return <>
<Typographyconst Typography: ({ as: Component, ...rest }: TypographyProps) => React.JSX.Element
as='foo' /> <Typographyconst Typography: ({ as: Component, ...rest }: TypographyProps) => React.JSX.Element
asas?: React.ElementType<any> | undefined
='div' />
</>
}
Polymorphic props with generic types
Now, we need to properly type the acceptable props based on the as
prop. For
example, if as
is label
, we should be allowed to pass htmlFor
. By default
the component will be a span
element, so it should not accept htmlFor
.
To do this, we will need to use a generic type:
// TODO: implement PropsOf
type PropsOftype PropsOf<T> = {}
<Tfunction (type parameter) T in type PropsOf<T>
> = {}
type TypographyPropstype TypographyProps<T extends React.ElementType> = {
as?: T;
}
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> = {
asas?: T | undefined
?: Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType>
} & PropsOftype PropsOf<T> = {}
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType>
>
const Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
= <Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'>({
asas: T | undefined
,
...restrest: {}
}: TypographyPropstype TypographyProps<T extends React.ElementType> = {
as?: T;
}
<Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
>) => {
const Componentconst Component: T | "span"
= asas: T | undefined
?? 'span'
return <Componentconst Component: T | "span"
{...restrest: {}
} />
}
Let’s implement PropsOf
now. We need a type that receives a valid React
element such as button
or label
, and returns an object with the element’s
valid props.
Fortunately, there is a type already for this:
React.ComponentPropsWithoutRef
.
type PropsOftype PropsOf<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> = React.ComponentPropsWithoutReftype React.ComponentPropsWithoutRef<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
>
type TypographyPropstype TypographyProps<T extends React.ElementType = React.ElementType<any>> = {
as?: T;
} & React.PropsWithoutRef<React.ComponentProps<T>>
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = React.ElementType<any>>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> = {
asas?: T | undefined
?: Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = React.ElementType<any>>
} & PropsOftype PropsOf<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = React.ElementType<any>>
>
const Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
= <Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'>({
asas: T | undefined
,
...restrest: Omit<TypographyProps<T>, "as">
}: TypographyPropstype TypographyProps<T extends React.ElementType = React.ElementType<any>> = {
as?: T;
} & React.PropsWithoutRef<React.ComponentProps<T>>
<Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
>) => {
const Componentconst Component: T | "span"
= asas: T | undefined
?? 'span'
return <Componentconst Component: T | "span"
{...restrest: Omit<TypographyProps<T>, "as">
} />
}
export default function Appfunction App(): React.JSX.Element
() {
return (
<>
{/* These should be ok */}
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
data-foodata-foo: string
='bar' />
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
asas?: "label" | undefined
="label" htmlForhtmlFor?: string | undefined
="my-input" />
{/* This should not, span elements don't accept htmlFor */}
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
htmlFor="my-input" /> {/* Neither this, div elements don't accept htmlFor */}
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
asas?: "div" | undefined
="div" htmlFor="my-input" /> </>
)
}
Fortunately, the current implementation already handles custom components —
just pass it to as
and it should just work.
For instance, imagine we want to create a link using Link
from
react-router-dom
or next/link
.
// A mock Link component
function Linkfunction Link(props: {
to: string;
}): React.JSX.Element
(propsprops: {
to: string;
}
: { toto: string
: string }) {
return <aJSX.IntrinsicElements.a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
hrefReact.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined
={propsprops: {
to: string;
}
.toto: string
} />
}
export default function Appfunction App(): React.JSX.Element
() {
return (
<>
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
asas?: ((props: {
to: string;
}) => React.JSX.Element) | undefined
={Linkfunction Link(props: {
to: string;
}): React.JSX.Element
} toto: string
='#' />
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
to='#' /> </>
)
}
Adding component own props type
Now, let’s say we want our Typography
component to have their own props, such
as a variant
prop. How should we do this while maintaining polymorphism?
We can do this by creating a new generic type, called PolymorphicProps
, that
accepts the element type and the component own props as arguments.
With these arguments, we will use the element type to get all props accepted by
that element’s type plus the component own props, while also omitting any
potential conflicting props between the two with DistributiveOmit
:
type DistributiveOmittype DistributiveOmit<T, U> = T extends any ? Pick<T, Exclude<keyof T, U>> : never
<Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Ufunction (type parameter) U in type DistributiveOmit<T, U>
> = Tfunction (type parameter) T in type DistributiveOmit<T, U>
extends any
? Picktype Pick<T, K extends keyof T> = { [P in K]: T[P]; }
From T, pick a set of properties whose keys are in the union K<Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Excludetype Exclude<T, U> = T extends U ? never : T
Exclude from T those types that are assignable to U<keyof Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Ufunction (type parameter) U in type DistributiveOmit<T, U>
>>
: never
type PropsOftype PropsOf<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> = React.ComponentPropsWithoutReftype React.ComponentPropsWithoutRef<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
>
type PolymorphicPropstype PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}> = {
as?: T;
} & TProps & DistributiveOmit<React.PropsWithoutRef<React.ComponentProps<T>>, keyof TProps | "as">
<
Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
,
TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
= {}
> = {
asas?: T | undefined
?: Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
} & TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
&
DistributiveOmittype DistributiveOmit<T, U> = T extends any ? Pick<T, Exclude<keyof T, U>> : never
<PropsOftype PropsOf<T extends React.ElementType> = React.ComponentProps<T> extends any ? "ref" extends keyof React.ComponentProps<T> ? Omit<React.ComponentProps<T>, keyof React.ComponentProps<T> & "ref"> : React.ComponentProps<...> : React.ComponentProps<...>
<Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
>, keyof TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
| 'as'>
type BaseTypographyPropstype BaseTypographyProps = {
variant: "heading" | "paragraph";
}
= {
variantvariant: "heading" | "paragraph"
: 'heading' | 'paragraph'
}
type TypographyPropstype TypographyProps<T extends React.ElementType = "span"> = {
as?: T | undefined;
} & BaseTypographyProps & DistributiveOmit<React.PropsWithoutRef<React.ComponentProps<T>>, "as" | "variant">
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = "span">
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'> = PolymorphicPropstype PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}> = {
as?: T;
} & TProps & DistributiveOmit<React.PropsWithoutRef<React.ComponentProps<T>>, keyof TProps | "as">
<
Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = "span">
,
BaseTypographyPropstype BaseTypographyProps = {
variant: "heading" | "paragraph";
}
>
const Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
= <Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'>({
asas: T | undefined
,
...restrest: Omit<TypographyProps<T>, "as">
}: TypographyPropstype TypographyProps<T extends React.ElementType = "span"> = {
as?: T | undefined;
} & BaseTypographyProps & DistributiveOmit<React.PropsWithoutRef<React.ComponentProps<T>>, "as" | "variant">
<Tfunction (type parameter) T in <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>): React.JSX.Element
>) => {
const Componentconst Component: "span" | T
= asas: T | undefined
?? 'span'
// @ts-expect-error: FIXME this used to work in TypeScript 4.x but not anymore
return <Componentconst Component: "span" | T
{...restrest: Omit<TypographyProps<T>, "as">
} />
}
export default function Appfunction App(): React.JSX.Element
() {
return (
<>
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
variantvariant: "heading" | "paragraph"
="heading" data-foodata-foo: string
="bar" />
<Typographyconst Typography: <T extends React.ElementType = "span">({ as, ...rest }: TypographyProps<T>) => React.JSX.Element
variantvariant: "heading" | "paragraph"
="paragraph" asas?: "label" | undefined
="label" htmlForhtmlFor?: string | undefined
="my-input" />
</>
)
}
The way we handle potential conflicts between the two is by using Omit
type.
This will omit all props in the result of PropsOf
that are also in TProps
,
i.e. TProps
will take precedence.
Type-safe polymorphic refs
Now let’s make our component ref
props strongly-typed, conditional on the
as
prop. Here’s what I mean by this exactly:
import { useRef } from 'react'
import { Typography } from './Typography'
export default function App() {
const buttonRef = useRef<HTMLButtonElement>(null)
return (
<>
{/* This should be fine */}
<Typography as="button" ref={buttonRef} variant='paragraph' />
{/* This should be a type error... */}
<Typography as="label" ref={buttonRef} variant='paragraph' />
</>
)
}
Our component does not accept a ref
prop yet. To do this, we need to change
the PropsOf
type to use React.ComponentPropsWithRef
instead of
React.ComponentPropsWithoutRef
:
type PropsOf<T extends React.ElementType> = React.ComponentPropsWithRef<T>
That’s the easy part but, of course, we didn’t forward any refs with that, we
actually need to use the
forwardRef
function.
Unfortunately, that’s where things start to get difficult — the forwardRef
doesn’t seem to be ergonomic enough to handle the polymorphic component case.
To illustrate this, let’s see how we’re supposed to type a non-polymorphic
component using the forwardRef
with TypeScript:
import { forwardReffunction forwardRef<T, P = {}>(render: React.ForwardRefRenderFunction<T, P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<T>>
, useReffunction useRef<T>(initialValue: T): React.MutableRefObject<T> (+2 overloads)
`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
(`initialValue`). The returned object will persist for the full lifetime of the component.
Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
value around similar to how you’d use instance fields in classes. } from 'react'
type TypographyPropstype TypographyProps = {
variant: "heading" | "paragraph";
}
= {
variantvariant: "heading" | "paragraph"
: 'heading' | 'paragraph'
}
const Typographyconst Typography: React.ForwardRefExoticComponent<TypographyProps & React.RefAttributes<HTMLSpanElement>>
= forwardRefforwardRef<HTMLSpanElement, TypographyProps>(render: React.ForwardRefRenderFunction<HTMLSpanElement, TypographyProps>): React.ForwardRefExoticComponent<...>
<HTMLSpanElement, TypographyPropstype TypographyProps = {
variant: "heading" | "paragraph";
}
>(
(propsprops: TypographyProps
, refref: React.ForwardedRef<HTMLSpanElement>
) => {
return <spanJSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
refReact.ClassAttributes<HTMLSpanElement>.ref?: React.LegacyRef<HTMLSpanElement> | undefined
Allows getting a ref to the component instance.
Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref).={refref: React.ForwardedRef<HTMLSpanElement>
} {...propsprops: TypographyProps
} />
}
)
As you can see, the function forwardRef
is itself a generic function, taking
two arguments: the ref
element’s type and the component’s props.
But this won’t help… to achieve what we want, we would need to pass as first
argument a type that is conditional on the as
prop value!
The problem doesn’t seem solvable unless we resort to type annotations/type
casting, as this article by Ben Ilegbodu
demonstrates.
So we essentially need to bypass the React.forwardRef
types and define the
component types from scratch.
import React, { useReffunction useRef<T>(initialValue: T): React.MutableRefObject<T> (+2 overloads)
`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
(`initialValue`). The returned object will persist for the full lifetime of the component.
Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
value around similar to how you’d use instance fields in classes. } from 'react'
type DistributiveOmittype DistributiveOmit<T, U> = T extends any ? Pick<T, Exclude<keyof T, U>> : never
<Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Ufunction (type parameter) U in type DistributiveOmit<T, U>
> = Tfunction (type parameter) T in type DistributiveOmit<T, U>
extends any
? Picktype Pick<T, K extends keyof T> = { [P in K]: T[P]; }
From T, pick a set of properties whose keys are in the union K<Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Excludetype Exclude<T, U> = T extends U ? never : T
Exclude from T those types that are assignable to U<keyof Tfunction (type parameter) T in type DistributiveOmit<T, U>
, Ufunction (type parameter) U in type DistributiveOmit<T, U>
>>
: never
type PropsOftype PropsOf<T extends React.ElementType> = T extends new (props: infer P) => React.Component<any, any> ? React.PropsWithoutRef<P> & React.RefAttributes<InstanceType<T>> : React.PropsWithRef<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> = React.ComponentPropsWithReftype React.ComponentPropsWithRef<T extends React.ElementType> = T extends new (props: infer P) => React.Component<any, any> ? React.PropsWithoutRef<P> & React.RefAttributes<InstanceType<T>> : React.PropsWithRef<...>
<Tfunction (type parameter) T in type PropsOf<T extends React.ElementType>
>
type PolymorphicReftype PolymorphicRef<T extends React.ElementType> = React.ComponentPropsWithRef<T>["ref"]
<Tfunction (type parameter) T in type PolymorphicRef<T extends React.ElementType>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
> =
React.ComponentPropsWithReftype React.ComponentPropsWithRef<T extends React.ElementType> = T extends new (props: infer P) => React.Component<any, any> ? React.PropsWithoutRef<P> & React.RefAttributes<InstanceType<T>> : React.PropsWithRef<...>
<Tfunction (type parameter) T in type PolymorphicRef<T extends React.ElementType>
>['ref']
type PolymorphicPropstype PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}> = {
as?: T;
} & TProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | keyof TProps | "as"> & {
ref?: PolymorphicRef<T>;
}
<
Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
,
TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
= {}
> = {
asas?: T | undefined
?: Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
} & TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
&
DistributiveOmittype DistributiveOmit<T, U> = T extends any ? Pick<T, Exclude<keyof T, U>> : never
<PropsOftype PropsOf<T extends React.ElementType> = T extends new (props: infer P) => React.Component<any, any> ? React.PropsWithoutRef<P> & React.RefAttributes<InstanceType<T>> : React.PropsWithRef<...>
<Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
>, keyof TPropsfunction (type parameter) TProps in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
| 'as' | 'ref'> & { refref?: PolymorphicRef<T> | undefined
?: PolymorphicReftype PolymorphicRef<T extends React.ElementType> = React.ComponentPropsWithRef<T>["ref"]
<Tfunction (type parameter) T in type PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}>
> }
type BaseTypographyPropstype BaseTypographyProps = {
variant: "heading" | "paragraph";
}
= {
variantvariant: "heading" | "paragraph"
: 'heading' | 'paragraph'
}
type TypographyPropstype TypographyProps<T extends React.ElementType = "span"> = {
as?: T | undefined;
} & BaseTypographyProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | "as" | "variant"> & {
...;
}
<Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = "span">
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'> = PolymorphicPropstype PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}> = {
as?: T;
} & TProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | keyof TProps | "as"> & {
ref?: PolymorphicRef<T>;
}
<
Tfunction (type parameter) T in type TypographyProps<T extends React.ElementType = "span">
,
BaseTypographyPropstype BaseTypographyProps = {
variant: "heading" | "paragraph";
}
>
type TypographyComponenttype TypographyComponent = <T extends React.ElementType = "span">(props: PolymorphicProps<T, TypographyProps<T>>) => JSX.Element | null
= <Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: PolymorphicProps<T, TypographyProps<T>>): JSX.Element | null
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'>(
propsprops: PolymorphicProps<T, TypographyProps<T>>
: PolymorphicPropstype PolymorphicProps<T extends React.ElementType = React.ElementType<any>, TProps = {}> = {
as?: T;
} & TProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | keyof TProps | "as"> & {
ref?: PolymorphicRef<T>;
}
<Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: PolymorphicProps<T, TypographyProps<T>>): JSX.Element | null
, TypographyPropstype TypographyProps<T extends React.ElementType = "span"> = {
as?: T | undefined;
} & BaseTypographyProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | "as" | "variant"> & {
...;
}
<Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: PolymorphicProps<T, TypographyProps<T>>): JSX.Element | null
>>
) => JSX.Elementinterface JSX.Element
| null
const Typographyconst Typography: TypographyComponent
: TypographyComponenttype TypographyComponent = <T extends React.ElementType = "span">(props: PolymorphicProps<T, TypographyProps<T>>) => JSX.Element | null
= React.forwardReffunction React.forwardRef<unknown, TypographyProps<React.ElementType<any>>>(render: React.ForwardRefRenderFunction<unknown, TypographyProps<React.ElementType<any>>>): React.ForwardRefExoticComponent<...>
(
<Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: TypographyProps<T>, ref: PolymorphicRef<T>): React.JSX.Element
extends React.ElementTypetype React.ElementType<P = any> = (P extends React.SVGProps<SVGSymbolElement> ? "symbol" : never) | (P extends React.DetailedHTMLProps<React.ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement> ? "object" : never) | ... 174 more ... | React.ComponentType<...>
= 'span'>(
propsprops: TypographyProps<T>
: TypographyPropstype TypographyProps<T extends React.ElementType = "span"> = {
as?: T | undefined;
} & BaseTypographyProps & DistributiveOmit<React.ComponentPropsWithRef<T>, "ref" | "as" | "variant"> & {
...;
}
<Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: TypographyProps<T>, ref: PolymorphicRef<T>): React.JSX.Element
>,
refref: PolymorphicRef<T>
: PolymorphicReftype PolymorphicRef<T extends React.ElementType> = React.ComponentPropsWithRef<T>["ref"]
<Tfunction (type parameter) T in <T extends React.ElementType = "span">(props: TypographyProps<T>, ref: PolymorphicRef<T>): React.JSX.Element
>
) => {
const { asconst as: T | undefined
, ...restconst rest: Omit<TypographyProps<T>, "as">
} = propsprops: TypographyProps<T>
const Componentconst Component: "span" | T
= asconst as: T | undefined
?? 'span'
return <Componentconst Component: "span" | T
refref: PolymorphicRef<T>
={refref: PolymorphicRef<T>
} {...restconst rest: Omit<TypographyProps<T>, "as">
} />
}
)
export default function Appfunction App(): React.JSX.Element
() {
const buttonRefconst buttonRef: React.RefObject<HTMLButtonElement>
= useRefuseRef<HTMLButtonElement>(initialValue: HTMLButtonElement | null): React.RefObject<HTMLButtonElement> (+2 overloads)
`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
(`initialValue`). The returned object will persist for the full lifetime of the component.
Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
value around similar to how you’d use instance fields in classes.
Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
of the generic argument.<HTMLButtonElement>(null)
return (
<>
<Typographyconst Typography: TypographyComponent
asas?: "button" | undefined
="button" refref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined
={buttonRefconst buttonRef: React.RefObject<HTMLButtonElement>
} variantvariant: "heading" | "paragraph"
="paragraph" />
<Typographyconst Typography: TypographyComponent
asas?: "label" | undefined
="label" ref={buttonRefconst buttonRef: React.RefObject<HTMLButtonElement>
} variantvariant: "heading" | "paragraph"
="paragraph" />
{/**
* Apparently this is fine... because HTMLSpanElement implements the
* HTMLElement interface, which the HTMLButtonElement inherits from.
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLSpanElement
*/}
<Typographyconst Typography: TypographyComponent
refref?: ((instance: HTMLSpanElement | null) => void) | React.RefObject<HTMLSpanElement> | null | undefined
={buttonRefconst buttonRef: React.RefObject<HTMLButtonElement>
} variantvariant: "heading" | "paragraph"
="paragraph" />
</>
)
}