Skip to content

Instantly share code, notes, and snippets.

@danilobjr
Created March 8, 2024 14:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danilobjr/763b26c18c6727a5356f5fd469aa8551 to your computer and use it in GitHub Desktop.
Save danilobjr/763b26c18c6727a5356f5fd469aa8551 to your computer and use it in GitHub Desktop.

Polymorphic React Type

type AsProp<C extends React.ElementType> = {
  as?: C
}

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P)

/**
 * @example
 *
 * type TextProps<C extends React.ElementType> =
 *   PolymorphicComponentProp<
 *     C,
 *     { someProp?: string }
 *   >
 *
 * export const Text = <C extends React.ElementType = "span">({
 *   as,
 *   someProp = '',
 *   children = null,
 * }: TextProps<C>) => {
 *   const Component = as || 'span'
 *
 *   console.log(someProp)
 *
 *   return (
 *     <Component {...style}>
 *       {children}
 *     </Component>
 *   )
 * })
 */
export type PolymorphicComponentProp<
  C extends React.ElementType,
  Props,
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>

/**
 * @example
 *
 * type TextProps<C extends ElementType> = PolymorphicComponentPropWithRef<
 *   C,
 *   { color?: Rainbow | 'black' }
 * >
 *
 * export const Text = forwardRef(
 *   <C extends ElementType = 'span'>(
 *     { as, color, children }: TextProps<C>,
 *     ref?: PolymorphicRef<C>,
 *   ) => {
 *     const Component = as || 'span'
 *
 *     const style = color ? { style: { color } } : {}
 *
 *     return (
 *       <Component {...style} ref={ref}>
 *         {children}
 *       </Component>
 *     )
 *   },
 * )
 */
export type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props,
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> }

/**
 * @example
 *
 * type TextProps<C extends ElementType> = PolymorphicComponentPropWithRef<
 *   C,
 *   { color?: Rainbow | 'black' }
 * >
 *
 * export const Text = forwardRef(
 *   <C extends ElementType = 'span'>(
 *     { as, color, children }: TextProps<C>,
 *     ref?: PolymorphicRef<C>,
 *   ) => {
 *     const Component = as || 'span'
 *
 *     const style = color ? { style: { color } } : {}
 *
 *     return (
 *       <Component {...style} ref={ref}>
 *         {children}
 *       </Component>
 *     )
 *   },
 * )
 */
export type PolymorphicRef<C extends React.ElementType> =
  React.ComponentPropsWithRef<C>['ref']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment