On this fast tip, excerpted from Unleashing the Power of TypeScript, Steve reveals you how you can use polymorphic elements in TypeScript.
In my article Extending the Properties of an HTML Element in TypeScript, I instructed you that, over the course of constructing out a big software, I have a tendency to finish up making a number of wrappers round elements. Field
is a primitive wrapper across the fundamental block components in HTML (resembling <div>
, <apart>
, <part>
, <article>
, <major>
, <head>
, and so forth). However simply as we donβt need to lose all of the semantic that means we get from these tags, we additionally donβt want a number of variations of Field
which are all principally the identical. What weβd love to do is use Field
but additionally have the ability to specify what it should be below the hood. A polymorphic part is a single adaptable part that may symbolize completely different semantic HTML components, with TypeScript robotically adjusting to those adjustments.
Right hereβs an excessively simplified tackle a Field
factor impressed by Styled Components.
And right hereβs an instance of a Field
part from Paste, Twilioβs design system:
<Field as="article" backgroundColor="colorBackgroundBody" padding="space60">
Mum or dad field on the hill facet
<Field
backgroundColor="colorBackgroundSuccessWeakest"
show="inline-block"
padding="space40"
>
nested field 1 made out of ticky cheesy
</Field>
</Field>
Right hereβs a easy implementation that doesnβt have any go by way of any of the props, like we did with Button
and LabelledInputProps
above:
import { PropsWithChildren } from 'react';
sort BoxProps = PropsWithChildren< 'part' >;
const Field = ({ as, kids }: BoxProps) => {
const TagName = as || 'div';
return <TagName>{kids}</TagName>;
};
export default Field;
We refine as
to TagName
, which is a legitimate part identify in JSX. That works as far a React is worried, however we additionally need to get TypeScript to adapt accordingly to the factor weβre defining within the as
prop:
import { ComponentProps } from 'react';
sort BoxProps = ComponentProps<'div'> & 'part' ;
const Field = ({ as, kids }: BoxProps) => {
const TagName = as || 'div';
return <TagName>{kids}</TagName>;
};
export default Field;
I actually donβt even know if components like <part>
have any properties {that a} <div>
doesnβt. Whereas Iβm certain I might look it up, none of us be ok with this implementation.
However whatβs that 'div'
being handed in there and the way does it work? If we take a look at the kind definition for ComponentPropsWithRef
, we see the next:
sort ComponentPropsWithRef<T extends ElementType> = T extends new (
props: infer P,
) => Element<any, any>
? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
: PropsWithRef<ComponentProps<T>>;
We are able to ignore all of these ternaries. Weβre enthusiastic about ElementType
proper now:
sort BoxProps = ComponentPropsWithRef<'div'> & {
as: ElementType;
};
Okay, thatβs attention-grabbing, however what if we wished the kind argument we give to ComponentProps
to be the identical as β¦ as
?
We might strive one thing like this:
import { ComponentProps, ElementType } from 'react';
sort BoxProps<E extends ElementType> = Omit<ComponentProps<E>, 'as'> & {
as?: E;
};
const Field = <E extends ElementType = 'div'>({ as, ...props }: BoxProps<E>) => {
const TagName = as || 'div';
return <TagName {...props} />;
};
export default Field;
Now, a Field
part will adapt to no matter factor sort we go in with the as
prop.
We are able to now use our Field
part wherever we would in any other case use a <div>
:
<Field as="part" className="flex place-content-between w-full">
<Button className="button" onClick={decrement}>
Decrement
</Button>
<Button onClick={reset}>Reset</Button>
<Button onClick={increment}>Increment</Button>
</Field>
You possibly can see the ultimate outcome on the polymorphic
branch of the GitHub repo for this tutorial.
This text is excerpted from Unleashing the Power of TypeScript, accessible on Pylogix Premium and from e-book retailers.