Skip to content

Type-safe `as` prop utility for flexible and semantic UI components of React

License

Notifications You must be signed in to change notification settings

neet/react-as-prop

Repository files navigation

react-as-prop

npm CI codecov

Thumbnail

Type-safe React utility that adds an ad-hoc prop for switching which HTML element to render, for developing flexible and semantic UI components.

Inspired by styled-component's as prop and Material UI's component prop.

Install

yarn add react-as-prop

Usage

overridable(component: FC, fallback: ElementType): FC

Adds as prop to the component specified in the argument.

  • component ― Component to add as prop
  • fallback ─ Default element, such as button or div

Here is an example of your component definition.

import { overridable } from "react-as-prop";

interface InternalButtonProps {
  // ⚠️ NOTE: This prop is always needed
  as: ElementType;
  size: "small" | "large";
  children?: ReactNode;
}

const InternalButton: FC<InternalButtonProps> = (props) => {
  const { as: Component, size, children, ...rest } = props;

  return (
    // You always have to add {...rest} in the end to accept other props from the component overriding this one
    <Component className={`button blue button-${size}`} {...rest}>
      {children}
    </Component>
  );
};

// It is recommended to export only this part
export const Button = overridable(Button, "button");
export type ButtonProps = ComponentProps<typeof Button>;

Then, it can be overriden with another component

<Button as="div" />
<Button as="a" href="/page" />
<Button as={Link} to="/" />

overridableWithRef(fn: ForwardRefRenderFunction, fallback: ElementType): FC

Almost same as overridable, but also supports type-safe forwardRef.

  • fn ― A function that accepts props and forwardedRef
  • fallback ─ Default element, such as button or div

Here is an example of your component definition.

import { overridableWithRef } from "react-as-prop";

interface InternalButtonProps {
  as: ElementType;
  size: "small" | "large";
  children?: ReactNode;
}

const InternalButton: ForwardRefRenderFunction<
  InternalButtonProps,
  // Use `unknown` because you can override it
  unknown
> = (props, forwardedRef) => {
  const { as: Component, size, children, ...rest } = props;

  return (
    <Component
      className={`button blue button-${size}`}
      ref={forwardedRef}
      {...rest}
    >
      {children}
    </Component>
  );
};

export const Button = overridableWithRef(Button, "button");
export type ButtonProps = ComponentProps<typeof Button>;

Then, it can be overriden with another component

const ref = useRef<HTMLAnchorElement | null>(null);

<Button as="a" href="/page" ref={ref} />
<Button as={Link} to="/" ref={ref} />

configure(propName: string): ConfigureResult

A factory function that returns override and overridableWithRef with customized name for as prop.

  • propName ― name of the prop to use instead of "as"
import { configure } from "react-as-prop";

// Use "kind" for as-prop
const { overridable } = configure("kind");

const Button = overridable(InternalButton, "button");
<Button kind="a" href="/" />;