-6.1 C
New York
Thursday, January 18, 2024

Creating Polymorphic Parts in TypeScript


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

In my article Extending the Properties of an HTML Aspect in TypeScript, I advised you that, over the course of constructing out a big utility, I have a tendency to finish up making a couple of wrappers round elements. Field is a primitive wrapper across the primary block parts in HTML (corresponding to <div>, <apart>, <part>, <article>, <principal>, <head>, and so forth). However simply as we don’t wish to lose all of the semantic which 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 signify totally different semantic HTML parts, with TypeScript routinely adjusting to those modifications.

Right here’s an excessively simplified tackle a Field ingredient 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">
  Guardian 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 cross by any of the props, like we did with Button and LabelledInputProps above:

import { PropsWithChildren } from 'react';

kind BoxProps = PropsWithChildren< 'article' >;

const Field = ({ as, youngsters }: BoxProps) => {
  const TagName = as || 'div';
  return <TagName>{youngsters}</TagName>;
};

export default Field;

We refine as to TagName, which is a sound part title in JSX. That works as far a React is worried, however we additionally wish to get TypeScript to adapt accordingly to the ingredient we’re defining within the as prop:

import { ComponentProps } from 'react';

kind BoxProps = ComponentProps<'div'> &  'article' ;

const Field = ({ as, youngsters }: BoxProps) => {
  const TagName = as || 'div';
  return <TagName>{youngsters}</TagName>;
};

export default Field;

I actually don’t even know if parts like <part> have any properties {that a} <div> doesn’t. Whereas I’m positive 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 have a look at the sort definition for ComponentPropsWithRef, we see the next:

kind 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 focused on ElementType proper now:

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

A autocompleted list of all of the element types

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

We might attempt one thing like this:

import { ComponentProps, ElementType } from 'react';

kind 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 ingredient kind we cross 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}>
    Decrement
  </Button>
  <Button onClick={reset}>Reset</Button>
  <Button onClick={increment}>Increment</Button>
</Field>

You may see the ultimate consequence 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.



Supply hyperlink

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles