Extending the Houses of an HTML Part in TypeScript — SitePoint

Extending the Houses of an HTML Part in TypeScript — SitePoint

[ad_1]

On this fast tip, excerpted from Unleashing the Energy of TypeScript, Steve displays you how one can lengthen the homes of an HTML component in TypeScript.

In many of the greater packages and tasks I’ve labored on, I ceaselessly in finding myself development a host of elements which can be in point of fact supersets or abstractions on most sensible of the usual HTML components. Some examples come with customized button components that would possibly take a prop defining whether or not or now not that button must be a most important or secondary button, or perhaps person who signifies that it’s going to invoke a deadly motion, reminiscent of deleting or taking out a merchandise from the database. I nonetheless need my button to have the entire homes of a button along with the props I wish to upload to it.

Any other commonplace case is that I’ll finally end up developing an element that permits me to outline a label and an enter box immediately. I don’t wish to re-add all the homes that an <enter /> component takes. I need my customized element to act identical to an enter box, however additionally take a string for the label and routinely twine up the htmlFor prop at the <label /> to correspond with the identity at the <enter />.

In JavaScript, I will simply use {...props} to move via any props to an underlying HTML component. This is a bit trickier in TypeScript, the place I want to explicitly outline what props an element will settle for. Whilst it’s great to have fine-grained keep watch over over the precise sorts that my element accepts, it may be tedious to have so as to add in kind knowledge for each and every unmarried prop manually.

In sure situations, I want a unmarried adaptable element, like a <div>, that adjustments kinds in keeping with the present theme. As an example, perhaps I wish to outline what kinds must be used relying on whether or not or now not the consumer has manually enabled gentle or darkish mode for the UI. I don’t wish to redefine this element for each and every unmarried block component (reminiscent of <segment>, <article>, <apart>, and so forth). It must be capable to representing other semantic HTML components, with TypeScript routinely adjusting to those adjustments.

There are a few methods that we will be able to make use of:

  • For elements the place we’re developing an abstraction over only one more or less component, we will be able to lengthen the homes of that component.
  • For elements the place we wish to outline other components, we will be able to create polymorphic elements. A polymorphic element is an element designed to render as other HTML components or elements whilst keeping up the similar homes and behaviors. It lets in us to specify a prop to resolve its rendered component kind. Polymorphic elements be offering flexibility and reusability with out us having to reimplement the element. For a concrete instance, you’ll be able to take a look at Radix’s implementation of a polymorphic element.

On this educational, we’ll take a look at the primary technique.

Mirroring and Extending the Houses of an HTML Part

Let’s get started with that first instance discussed within the creation. We wish to create a button that comes baked in with the precise styling to be used in our utility. In JavaScript, we could possibly do one thing like this:

const Button = (props) => {
  go back <button className="button" {...props} />;
};

In TypeScript, lets simply upload what we all know we want. As an example, we all know that we want the youngsters if we wish our customized button to act the similar means an HTML button does:

const Button = ({ youngsters }: React.PropsWithChildren) => {
  go back <button className="button">{youngsters}</button>;
};

You’ll be able to believe that including homes separately may get a little tedious. As a substitute, we will be able to inform TypeScript that we wish to fit the similar props that it will use for a <button> component in React:

const Button = (props: React.ComponentProps<'button'>) => {
  go back <button className="button" {...props} />;
};

However now we have a brand new drawback. Or, slightly, we had an issue that additionally existed within the JavaScript instance and which we neglected. If anyone the usage of our new Button element passes in a className prop, it’s going to override our className. Lets (and we can) upload some code to maintain this in a second, however I don’t wish to move up the chance to turn you how one can use a application kind in TypeScript to mention “I wish to use all of the props from an HTML button with the exception of for one (or extra)”:

kind ButtonProps = Overlook<React.ComponentProps<'button'>, 'className'>;

const Button = (props: ButtonProps) => {
  go back <button className="button" {...props} />;
};

Now, TypeScript will forestall us or any person else from passing a className assets into our Button element. If we simply sought after to increase the category listing with no matter is handed in, lets do this in a couple of alternative ways. Lets simply append it to the listing:

kind ButtonProps = React.ComponentProps<'button'>;

const Button = (props: ButtonProps) => {
  const className="button " + props.className;

  go back <button className={className.trim()} {...props} />;
};

I love to make use of the clsx library when operating with categories, because it looks after all these forms of issues on our behalf:

import React from 'react';
import clsx from 'clsx';

kind ButtonProps = React.ComponentProps<'button'>;

const Button = ({ className, ...props }: ButtonProps) => {
  go back <button className={clsx('button', className)} {...props} />;
};

export default Button;

We realized how one can prohibit the props {that a} element will settle for. To increase the props, we will be able to use an intersection:

kind ButtonProps = React.ComponentProps<'button'> &  'secondary';
;

We’re now pronouncing that Button accepts all the props {that a} <button> component accepts plus yet another: variant. This prop will display up with the entire different props we inherited from HTMLButtonElement.

Variant shows up as a prop on our Button component

We will upload strengthen to our Button so as to add this elegance as smartly:

const Button = ({ variant, className, ...props }: ButtonProps) => {
  go back (
    <button
      className={clsx(
        'button',
        variant === 'most important' && 'button-primary',
        variant === 'secondary' && 'button-secondary',
        className,
      )}
      {...props}
    />
  );
};

We will now replace src/utility.tsx to make use of our new button element:

diff --git a/src/utility.tsx b/src/utility.tsx
index 978a61d..fc8a416 100644
--- a/src/utility.tsx
+++ b/src/utility.tsx
@@ -1,3 +1,4 @@
+import Button from './elements/button';
 import useCount from './use-count';

 const Counter = () => {
@@ -8,15 +9,11 @@ const Counter = () => {
       <h1>Counter</h1>
       <p className="text-7xl">{depend}</p>
       <div className="flex place-content-between w-full">
-        <button className="button" onClick={decrement}>
+        <Button onClick={decrement}>
           Decrement
-        </button>
-        <button className="button" onClick={reset}>
-          Reset
-        </button>
-        <button className="button" onClick={increment}>
-          Increment
-        </button>
+        </Button>
+        <Button onClick={reset}>Reset</Button>
+        <Button onClick={increment}>Increment</Button>
       </div>
       <div>
         <shape
@@ -32,9 +29,9 @@ const Counter = () => {
         >
           <label htmlFor="set-count">Set Rely</label>
           <enter kind="quantity" identity="set-count" identify="set-count" />
-          <button className="button-primary" kind="publish">
+          <Button variant="most important" kind="publish">
             Set
-          </button>
+          </Button>
         </shape>
       </div>
     </primary>

You’ll be able to in finding the adjustments above in the button department of the GitHub repo for this educational.

Developing Composite Elements

Any other commonplace element that I in most cases finally end up making for myself is an element that appropriately wires up a label and enter component with the proper for and identity attributes respectively. I generally tend to develop weary typing this out again and again:

<label htmlFor="set-count">Set Rely</label>
<enter kind="quantity" identity="set-count" identify="set-count" />

With out extending the props of an HTML component, I would possibly finally end up slowly including props as wanted:

kind LabeledInputProps =  quantity;
  kind?: string;
  className?: string;
  onChange?: ChangeEventHandler<HTMLInputElement>;
;

As we noticed with the button, we will be able to refactor it similarly:

kind LabeledInputProps = React.ComponentProps<'enter'> & {
  label: string;
};

Instead of label, which we’re passing to the (uhh) label that we’ll ceaselessly need grouped with our inputs, we’re manually passing props via one at a time. Will we wish to upload autofocus? Higher upload every other prop. It will be higher to do one thing like this:

import { ComponentProps } from 'react';

kind LabeledInputProps = ComponentProps<'enter'> & {
  label: string;
};

const LabeledInput = ({ identity, label, ...props }: LabeledInputProps) => {
  go back (
    <>
      <label htmlFor={identity}>{label}</label>
      <enter {...props} identity={identity} readOnly={!props.onChange} />
    </>
  );
};

export default LabeledInput;

We will change in our new element in src/utility.tsx:

<LabeledInput
  identity="set-count"
  label="Set Rely"
  kind="quantity"
  onChange={(e) => setValue(e.goal.valueAsNumber)}
  price={price}
/>

We will pull out the issues we want to paintings with after which simply move the entirety else on via to the <enter /> element, after which simply fake for the remainder of our days that it’s an ordinary HTMLInputElement.

TypeScript doesn’t care, since HTMLElement is lovely versatile, because the DOM pre-dates TypeScript. It most effective complains if we toss stuff totally egregious in there.

You’ll be able to see all the adjustments above in the enter department of the GitHub repo for this educational.

This text is excerpted from Unleashing the Energy of TypeScript, to be had on SitePoint Top class and from e book shops.



[ad_2]

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back To Top
0
Would love your thoughts, please comment.x
()
x