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
    nested field 1 made out of ticky cheesy

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;

A autocompleted list of all of the element types

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.

A Box with the props of a button

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}>
  <Button onClick={reset}>Reset</Button>
  <Button onClick={increment}>Increment</Button>

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.