React is a popular JavaScript library used for building user interfaces. One of the key features of React is the ability to create reusable components. Creating a polymorphic React component with TypeScript involves defining a component that can render different types of content based on props. This allows for flexibility and reusability within your application.
Table of Contents
Why used Polymorphic React component ?
In React development, polymorphic components offer remarkable flexibility by dynamically adapting their form based on user input. Leveraging TypeScript, we can construct these versatile components with robust type safety and enhanced maintainability.
Polymorphic components in React and TypeScript are components that can act in more than one way depending on how they are instantiated. This means that they can change their behavior based on the props they receive. A common use case for polymorphic components is when you want a component to be able to render as different HTML elements or custom React components.
Requirement or step for creating react polymorphic components with TypeScript
Include an essential "as"
prop of type React.ElementType
.
Creating a polymorphic component involves using TypeScript generics and the as
prop. The as
prop is used to control the render element of the polymorphic component. Here’s a basic implementation of a polymorphic component:
const MyComponent = ({ as, children }) => {
const Component = as || "span";
return <Component>{children}</Component>;
};
In this example, MyComponent
can be rendered as any valid React element type, with “span” being the default. If you pass an invalid as
prop, TypeScript will throw an error.
Embrace Generics for Type Safety:
Use TypeScript generics to ensure type safety. Create a generic type parameter for the “as” prop to accept only valid HTML element types or custom components.
Implement Shape-Shifting Logic:
Inspect the “as” prop to determine the output element. Use conditional rendering based on the “as” prop value and spread the remaining props to the rendered element/component.
Example 2 of react polymorphic components with TypeScript
This example demonstrates a basic button component that can render as either a <button>
or an <a>
element depending on the as
prop.
In this example, the Button
component accepts a generic type parameter E
that represents the desired output element type. The component’s props include the children
prop, the as
prop to determine the output element, and any other necessary button-specific props.
type ButtonProps<E extends React.ElementType = 'button'> = {
children: React.ReactNode;
// Other button-specific props like onClick, disabled, etc.
} & Omit<React.ComponentProps<E>, 'as'>; // Exclude the "as" prop
const Button: React.FC<ButtonProps> = ({ children, as = 'button', ...props }) => {
// Use the "as" prop to render the appropriate element
const TagName = as;
return <TagName {...props}>{children}</TagName>;
};
// Usage
<Button as="a" href="#">Click me!</Button>
<Button>Submit</Button> // Renders as button by default
Note: Omit: This is a utility type in TypeScript that takes two types and produces a new type that excludes the properties specified in the second type from the first type.
Omit<React.ComponentProps<E>, 'as'>
extracts all props of the specified element type E
except for the as
prop. This ensures that the as
prop does not conflict with the usage of as
for specifying the underlying element in the Button
component.
In this example, the ButtonProps
type defines the accepted props for the Button
component. It includes the children
prop, any other button-specific props, and uses Omit<React.ComponentProps<E>, 'as'>
to exclude the "as"
prop from the generic element type’s props. This ensures that other props specific to the chosen element (e.g., href
for anchor tags) are not mistakenly used with the button.
By using &
to combine the props, you are creating a new type that includes both the props specific to your component and the props of the chosen element type, excluding the 'as'
prop.
react polymorphic components with TypeScript example 2
import React from 'react';
// Generic component with React.FC and optional chaining
type BoxProps<C extends React.ElementType> = {
as?: C;
children: React.ReactNode;
} & React.ComponentPropsWithoutRef<C>;
const Box: React.FC<BoxProps<React.ElementType | 'header' | 'footer'>> = ({ as, children, ...rest }) => {
const Component = as || 'div';
// Styles with optional chaining
const styles = {
backgroundColor: Component === 'header' ? 'lightblue' : Component === 'footer' ? 'lightgreen' : undefined,
padding: '10px',
};
return <Component style={styles} {...rest}>{children}</Component>;
};
const Usage: React.FC = () => {
return (
<>
{/* Box with a div as the underlying element */}
<Box>
<h2>Title</h2>
<p>Content goes here.</p>
</Box>
{/* Box with a custom component as the underlying element */}
<Box as="section">
<img src="image.jpg" alt="A beautiful image" />
<p>More content here.</p>
</Box>
{/* Box with a header styling */}
<Box as="header">
<h1>Header</h1>
</Box>
{/* Box with a footer styling */}
<Box as="footer">
<p>Footer content</p>
</Box>
</>
);
};
export default Usage;
- The
BoxProps
type accepts a generic typeC
representing the underlying element or a set of predefined options ('header' | 'footer'
in this case). - The
Box
component usesComponentPropsWithoutRef
with the|
(union) option to allow users to choose between standard HTML elements and the additional predefined options. - The
styles
variable is used to apply different styles based on the chosen component, enhancing the polymorphic nature of the component.
/