Saturday, October 11, 2025
HomeProgrammingCreating Polymorphic Parts in TypeScript

Creating Polymorphic Parts in TypeScript


On this fast tip, excerpted from Unleashing the Energy of TypeScript, Steve reveals you methods to use polymorphic elements in TypeScript.

In my article Extending the Properties of an HTML Aspect in TypeScript, I instructed you that, over the course of constructing out a big software, I have a tendency to finish up making just a few wrappers round elements. Field is a primitive wrapper across the fundamental block parts in HTML (resembling <div>, <apart>, <part>, <article>, <most important>, <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 mainly the identical. What we’d love to do is use Field but in addition be capable of specify what it should be below the hood. A polymorphic part is a single adaptable part that may symbolize totally different semantic HTML parts, with TypeScript routinely adjusting to those adjustments.

Right here’s a very simplified tackle a Field component impressed by Styled Parts.

And right here’s an instance of a Field part from Paste, Twilio’s design system:

<Field as="article" backgroundColor="colorBackgroundBody" padding="space60">
  Dad or mum field on the hill aspect
  <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 component 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 truthfully don’t even know if parts like <part> have any properties {that a} <div> doesn’t. Whereas I’m positive I may 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 sort 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 will ignore all of these ternaries. We’re considering ElementType proper now:

sort BoxProps = ComponentPropsWithRef<'div'> & {
  as: ElementType;
};

A autocompleted list of all of the element types

Okay, that’s fascinating, however what if we wished the sort argument we give to ComponentProps to be the identical as … as?

We may 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 component sort we go in with the as prop.

A Box with the props of a button

We will 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’ll be able to see the ultimate outcome on the polymorphic department of the GitHub repo for this tutorial.

This text is excerpted from Unleashing the Energy of TypeScript, accessible on SitePoint Premium and from e book retailers.



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments