Skip to content

KingSora/react-as

Repository files navigation

react-as

Render react components as other components. Try it on the playground.
React Version License

Alt text

Why?

While developing with react I've experienced the need to adjust the tag or element type of my components. This was especially needed if I wanted the appearance of one component but the functionality of an other. Like rendering a Link as a Button, the link should have the functionality (and markup) of the Link component but the appearance of the Button component.

How was this solved until now?

The solution was to introduce an component, tag or as property to the components which had the appearance but not the functionality. In the Link and Button example the Button would receive such an property. Many popular libraries are doing like this: MUI, Ant Design, react-bootstrap. The problem is that if more components should receive the functionality of the Link component, the property needs to be re-implement over and over again for each component. And this is where react-as comes in! Not only can react-as replicate the current solution without re-implementing it over and over again, its also possible to give the Link component the as prop instead of the Button.

Similar approeaches

Until now I've only encountered the Radix UI Primitves library which uses this approach with their asChild component property.

Usage

The package comes with the As react component and the transform function. Both are exactly the same, but you can choose when to use what.

import As, { transform } from "react-as";
import { Link } from "react-router-dom";
import Button from "./Button";

// with <As /> component
const MyComponent = () => (
  <As
    component={<Button theme="primary">Button</Button>}
    as={<Link to="/home" />}
  />
);

// with transform function
const MyComponent = () =>
  transform(<Button theme="primary">Button</Button>, <Link to="/home" />);

How does it work?

JSX elements have a type called ElementType or ComponentType. This type is required and is always defined. This type can be split into 3 categories:

  1. intrinsic type for elements like div, p, a etc.
  2. exotic type for special react elements like Fragment, Context, Memo etc.
  3. custom type for your own Components

Simply put the library traverses the root elements of your components until it encounters a intrinsic element, because this means this is the element which most likely should be transformed. It creates a new custom render function where it changes the type of this element to your desired as component type.

If during the traversal no intrinsic element type could be found, the transformation is considered unsuccessful and you can decide with the strategy option how to handle the case.

API

As already mentioned, the package has two main functions:

  1. The As react component. (default and named export)
  2. The transform function. (named export only)

Both are doing exactly the same. The As component is just a wrapper for the transform function.

As Component Props

All props are optional and can be null or undefined as well.

name type default description
component JSXElementConstructor | JSX.Element | string undefined The component which shall be transformed.
as JSXElementConstructor | JSX.Element | string undefined The component into which the input component shall be transformed.
options Options undefined The component into which the input component shall be transformed.

Options

The options object gives you more control of the transformation process.

name type default description
strategy "wrap" | "leave" "wrap" The strategy how to continue in case the transformation process would fail.
wrap: wrap the "component" always with the "as" component
leave: leave the "component" alone and do nothing if the transformation wasn't successful
recursive boolean true Try to transform the component recursively if the transformation isn't successful after the first iteration.
overwriteProps OverwriteProps undefined A function which gets the props of both components as arguments and returns a tuple with the overwritten props.

OverwriteProps

This functions allows you to control which properties are passed to the transformed component. The function takes the componentProps as its first argument, the "as" component props as its second argument and should return an tuple which represents the overwritten props.

The simplified typescript type declaration is:

type OverwriteProps<CompProps, AsProps> = (
  compProps: CompProps,
  asProps: AsProps
) => [CompProps, AsProps];

Types

The package comes with the InputComponentProps helper type which is here to help you write a correctly typed OverwriteProps function. Because typescript can't interfere the type of a JSX.Element you have to do it yourself, For all other cases the OverwriteProps can interfere the correct props type for you automatically.

import As, { InputComponentProps }  from 'react-as';

<As
  component={<div>Div as Paragraph</div>}
  as={<MyComponent />}
  options={{
    overwriteProps: (componentProps: InputComponentProps<'div'>, asProps: InputComponentProps<typeof MyComponent>) => [componentProps, asProps],
  }}
/>

In the example above I'm using the InputComponentProps helper type to get the correct props type for the two components I'm transforming.

License

MIT

About

Render react components as other components.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published