In this quick tip, excerpted from Unleashing the Power of TypeScript, Steve demonstrates how to extend the properties of an HTML element using TypeScript. In many large applications and projects, developers often build components that are enhancements or abstractions of standard HTML elements. For example, custom button elements may have additional props such as defining whether the button should be primary or secondary, or indicating a dangerous action like deleting an item from a database. These custom components should still have all the properties of a standard button element in addition to the added props.
Another common scenario is creating a component that combines a label and an input field. Instead of re-adding all the properties of an `` element, the custom component should behave like an input field but also accept a string for the label and automatically associate the `htmlFor` prop on the `` with the `id` on the ``.
In JavaScript, you can use `{…props}` to pass through any props to an underlying HTML element. However, in TypeScript, you need to explicitly define the props that a component will accept. While it’s beneficial to have fine-grained control over the types of props a component accepts, it can be tedious to manually add type information for each prop. In some cases, you need a single adaptable component, like a `
There are two strategies to achieve this. For components that abstract only one type of element, you can extend the properties of that element. For components that need to support different elements, you can create polymorphic components. A polymorphic component can render as different HTML elements or components while maintaining the same properties and behaviors. It allows you to specify a prop to determine the rendered element type.
In this tutorial, we’ll focus on the first strategy – mirroring and extending the properties of an HTML element. Let’s start with the example of creating a custom button with appropriate styling for an application. In JavaScript, you might do something like this:
“`javascript
const Button = (props) => {
return ;
};
“`
In TypeScript, you can add the necessary props one by one. For example, you know that the custom button should have the `children` prop to behave like a standard HTML button:
“`typescript
const Button = ({ children }: React.PropsWithChildren) => {
return ;
};
“`
Adding properties one by one can become tedious, so you can tell TypeScript to match the same props as a `
“`typescript
const Button = (props: React.ComponentProps<'button'>) => {
return ;
};
“`
However, this approach has a problem. If someone using your custom button component passes a `className` prop, it will override the predefined `className`. To solve this, you can use a utility type in TypeScript to exclude specific props:
“`typescript
type ButtonProps = Omit
const Button = (props: ButtonProps) => {
return ;
};
“`
Now, TypeScript will prevent any `className` property from being passed into the Button component. To extend the class list with additional classes, you can use the `clsx` library:
“`typescript
import React from ‘react’;
import clsx from ‘clsx’;
type ButtonProps = React.ComponentProps<'button'>;
const Button = ({ className, …props }: ButtonProps) => {
return ;
};
export default Button;
“`
To extend the props of a component, you can use an intersection type:
“`typescript
type ButtonProps = React.ComponentProps<'button'> & { variant?: ‘primary’ | ‘secondary’ };
“`
Now, the Button component accepts all the props of a `
“`typescript
const Button = ({ variant, className, …props }: ButtonProps) => {
return (
);
};
“`
You can use the updated Button component in your application:
“`typescript
import Button from ‘./components/button’;
import useCount from ‘./use-count’;
const Counter = () => {
// …
return (
);
};
// …
“`
The tutorial also covers creating composite components, such as a component that connects a label and input element with the correct `for` and `id` attributes. Instead of manually adding props as needed, you can extend the props of an HTML element. For example:
“`typescript
type LabeledInputProps = React.ComponentProps<'input'> & { label: string };
“`
In this case, the LabeledInput component accepts all the props of an `` element plus an additional prop called `label`. You can use this component to simplify the code:
“`typescript
const LabeledInput = ({ id, label, …props }: LabeledInputProps) => {
return (
<>
>
);
};
export default LabeledInput;
“`
You can then use the LabeledInput component in your application:
“`typescript
“`
By extending the props of HTML elements and using TypeScript’s type system, you can create reusable and flexible components without the need to manually add type information for each prop.
Source link