Derived components
Sometimes you might have to extend a component with React logic. You can do this by creating a derived component.
A derived component might look like this:
- A base
classed
component that adds styles & variants - A React component wrapping it that adds useful logic like icons, loading bars etc.
deriveClassed
In order to create a wrapped component with a working as
prop you must wrap it in deriveClassed
. The function works similar to React.forwardRef
and takes a callback that receives the props and a ref. The callback should only return the classed
component you want to wrap.
A simple example looks like this:
import { classed, deriveClassed, ComponentProps } from "@tw-classed/react";
const ButtonBase = classed.button({
base: "px-2 py-4 flex items-center gap-2",
variants: {
color: {
blue: "bg-blue-500 text-white",
red: "bg-red-500 text-white",
},
},
});
export type ButtonProps = ComponentProps<typeof ButtonBase> & {
icon?: React.ReactNode; // Add an icon
};
export const Button = deriveClassed<typeof ButtonBase, ButtonProps>(
({ children, icon, ...rest }, ref) => {
return (
<ButtonBase {...rest} ref={ref}>
{icon && <span>{icon}</span>}
<span>{children}</span>
</ButtonBase>
);
}
);
// In your App
import { CheckIcon, LinkIcon } from "example-icons";
() => <Button color="blue">Click me</Button>;
() => <Button icon={<CheckIcon />}>Click me</Button>;
// Use the `as` prop to change the rendered ButtonBase element
() => (
<Button as="a" href="/" icon={<LinkIcon />}>
Docs
</Button>
);
In the example above we created a Button
component that wraps the ButtonBase
component. The Button
component adds an icon and a span around the children. The Button
component also accepts all props that the ButtonBase
component accepts.
This is a very simple example, but you can use this pattern to add any kind of logic to your classed
components.
NOTE The ref
must be forwarded in your derived function.
Experimental As
manual prop support
The deriveClassed
function also accepts a third argument to support the as
prop manually in its render function. This is an experimental feature and might be removed in the future.
In order to use this feature you must pass the as
prop type as the third generic argument to the deriveClassed
function. This is to allow the compiler to infer the correct as
prop type since its being manually set in the render function.
export const Anchor = deriveClassed<typeof ButtonBase, ButtonProps, "a">( // Notice the third argument.
({ children, icon, ...rest }, ref) => {
return (
<ButtonBase as="a" {...rest} ref={ref}>
{icon && <span>{icon}</span>}
<span>{children}</span>
</ButtonBase>
);
}
);
Using the TypeScript casting
API
A second option is to use the TypeScript casting
API to cast the component to a strict component.
See the TypeScript casting
API for more information.