Edupala

Comprehensive Full Stack Development Tutorial: Learn Ionic, Angular, React, React Native, and Node.js with JavaScript

Building a react polymorphic components with TypeScript

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.

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 type C representing the underlying element or a set of predefined options ('header' | 'footer' in this case).
  • The Box component uses ComponentPropsWithoutRef 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.

/

Building a react polymorphic components with TypeScript

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top