React
Deriving components

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:

  1. A base classed component that adds styles & variants
  2. 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.