A set of types to help easily create fast polymorphic components. This package heavily relied on react-polymorphic-types when it was being made.
Let's start with creating a polymorphic button component.
import { PolymorphicComponent } from "react-polymorphed";
type Props = {
size?: "small" | "large";
};
// pass it the default type and your own props
const Button: PolymorphicComponent<"button", Props> = ({
as: As = "button",
size,
...props
}) => {
return <As {...props} />;
};
We can then use this polymorphic component like so:
<Button type="submit" size="small"> I am a button!</Button>
<Button as={"a"} href="https://github.com/nasheomirro/react-polymorphed/blob/main/" size="large"> I became an achor!</Button>
<Button href="https://github.com/nasheomirro/react-polymorphed/blob/main/">I cannot have an href!</Button> //error
forwardRef()
The easiest way to create ref-forwarded polymorphic components is to cast the forwardRef
function to a PolyRefFunction
:
import { forwardRef } from "react";
import { PolyRefFunction } from "react-polymorphed";
const polyRef = forwardRef as PolyRefFunction;
type Props = {
size?: "small" | "large";
};
const Button = polyRef<"button", Props>(
({ as: As = "button", size, ...props }, ref) => {
return <As ref={ref} {...props} />;
}
);
This should now expose the ref property and will correctly change it's type based on the as
prop. If the component given to the as
prop does not support refs then it will not show.
const Example = () => {
const buttonRef = useRef<HTMLButtonElement>(null);
return (
<>
<Button ref={buttonRef} />
// error! type of ref don't match
<Button as="div" ref={buttonRef} />
// error! property ref doesn't exist
<Button as={() => null} ref={buttonRef} />
</>
);
};
memo()
and lazy()
Unlike React.forwardRef()
, memo and lazy doesn't need any special functions to make work, we can simply assign it's type correctly like so:
import React from "react";
import {
PolymorphicComponent,
PolyMemoComponent,
PolyLazyComponent,
} from "react-polymorphed";
type Props = {
size?: "small" | "large";
};
const Button: PolymorphicComponent<"button", Props> = ({
as: As = "button",
size,
...props
}) => {
return <As {...props} />;
};
const MemoButton: PolyMemoComponent<"button", Props> = React.memo(Button);
// in another file:
const LazyButton: PolyLazyComponent<"button", Props> = React.lazy(
async () => import("./Button")
);
memo()
and lazy()
with polyRef()
Note that if the polymorphic component forwards refs, you need to instead use either the PolyForwardMemoComponent
or PolyForwardLazyComponent
to correctly preserve the ref property.
import React from "react";
import { PolyRefFunction, PolyForwardMemoComponent } from "react-polymorphed";
const polyRef = React.forwardRef as PolyRefFunction;
type Props = {
size?: "small" | "large";
};
const RefButton = polyRef<"button", Props>(
({ as: As = "button", size, ...props }, ref) => {
return <As ref={ref} {...props} />;
}
);
// use the correct type!
const MemoRefButton: PolyForwardMemoComponent<"button", Props> =
React.memo(RefButton);
Say you wanted your button to only be "button" | "a"
, you can pass a third type to the PolymorphicComponent
with OnlyAs<T>
:
import React from "react";
import { PolymorphicComponent, OnlyAs } from "react-polymorphed";
const Button: PolymorphicComponent<"button", {}, OnlyAs<"button" | "a">> = ({
as: As = "button",
...props
}) => {
return <As {...props} />;
};
<Button />;
<Button as="a" />;
<Button as="div" />; // error!
⚠️ Hold up! It has occured to me that constraints may not be a good feature to use and could even do more harm than good so before you use constraints it is important that you read the FAQ below on why you might not want them.
OnlyAs