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.
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
.
Until now I've only encountered the Radix UI Primitves library which uses this approach with their asChild
component property.
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" />);
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:
- intrinsic type for elements like
div
,p
,a
etc. - exotic type for special react elements like
Fragment
,Context
,Memo
etc. - 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.
As already mentioned, the package has two main functions:
- The
As
react component. (default and named export) - The
transform
function. (named export only)
Both are doing exactly the same. The As
component is just a wrapper for the transform
function.
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. |
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. |
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];
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.
MIT