developer-tool

Building blocks for strongly typed polymorphic components in React

Feb 16, 2020
Building blocks for strongly typed polymorphic components in React

react-polymorphic-box

Building blocks for strongly typed polymorphic components in React.

react-polymorphic-box

???? Motivation

Popularized by Styled Components v4, the as prop allows changing the HTML tag rendered by a component, e.g.:

import { Box } from 'react-polymorphic-box'; import { Link } from 'react-router-dom'; <Box as="a" href="https://github.com/kripod">GitHub</Box> <Box as={Link} to="/about">About</Box>

While this pattern has been encouraged by several libraries, typings had lacked support for polymorphism, missing benefits like:

  • Automatic code completion, based on the value of the as prop
  • Static type checking against the associated component's inferred props
  • HTML element name validation

???? Usage

A Heading component can demonstrate the effectiveness of polymorphism:

<Heading color="rebeccapurple">Heading</Heading> <Heading as="h3">Subheading</Heading>

Custom components like the previous one may utilize the package as shown below.

import React from 'react'; import { Box, PolymorphicComponentProps } from 'react-polymorphic-box'; // Component-specific props should be specified separately export interface HeadingOwnProps { color?: string; } // Merge own props with others inherited from the underlying element type export type HeadingProps< E extends React.ElementType > = PolymorphicComponentProps<E, HeadingOwnProps>; // An HTML tag or a different React component can be rendered by default const defaultElement = 'h2'; export function Heading<E extends React.ElementType = typeof defaultElement>({ color, style, ...restProps }: HeadingProps<E>): JSX.Element { // The `as` prop may be overridden by the passed props return <Box as={defaultElement} style={{ color, ...style }} {...restProps} />; }

Forwarding Refs

Library authors should consider encapsulating reusable components, passing a ref through each of them:

import React from 'react'; import { Box } from 'react-polymorphic-box'; export const Heading = React.forwardRef( <E extends React.ElementType = typeof defaultElement>( { ref, color, style, ...restProps }: HeadingProps<E>, innerRef: typeof ref, ) => { return ( <Box ref={innerRef} as={defaultElement} style={{ color, ...style }} {...restProps} /> ); }, ) as <E extends React.ElementType = typeof defaultElement>( props: HeadingProps<E>, ) => JSX.Element;

GitHub

Recommended