Skip to content

How do I wrap my Polymorphic Component? #8

Open

Description

@mrbinky3000

Thank you for your excellent tutorial. I'm still new to Typescript.

I'm not sure how to write a component that wraps my Polymorphic created using your tutorial. Lets call the polymorphic component "Atom" for the sake of this question.

The wrapper component is called "Molecule" and I want "Molecule" to accept have all the same props as "Atom" plus a few more.

Should my wrapper component also use your Polymorphic types to take advantage of the strict typing? That's what I'm doing right now. There is a small problem, though. I can't pass the "as" prop from Molecule to Atom.

As an example, I've copied your example code for the Text component and also added a Wrapper component that wraps Text and provides an additional style. I can't use Wrapper to pass the "as" prop to the Text component.

import React from 'react';

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

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

// This is the first reusable type utility we built
type PolymorphicComponentProp<
  C extends React.ElementType,
  Props = object,
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

// This is a new type utility with ref!
type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props = object,
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> };

// This is the type for the "ref" only
type PolymorphicRef<C extends React.ElementType> =
  React.ComponentPropsWithRef<C>['ref'];

// -----------------------------------------------------------------------------
// Text Component
// -----------------------------------------------------------------------------

type Rainbow =
  | 'red'
  | 'orange'
  | 'yellow'
  | 'green'
  | 'blue'
  | 'indigo'
  | 'violet';

/**
 * This is the updated component props using PolymorphicComponentPropWithRef
 **/
type TextProps<C extends React.ElementType> = PolymorphicComponentPropWithRef<
  C,
  { color?: Rainbow | 'black' }
>;

/**
 * This is the type used in the type annotation for the component
 **/
type TextComponent = <C extends React.ElementType = 'span'>(
  props: TextProps<C>
) => React.ReactNode | null;

export const Text: TextComponent = React.forwardRef(function Text<
  C extends React.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>
  );
});

// -----------------------------------------------------------------------------
// Wrapper Component
// -----------------------------------------------------------------------------

/**
 * This is the updated component props using PolymorphicComponentPropWithRef
 **/
type WrapperProps<C extends React.ElementType> =
  PolymorphicComponentPropWithRef<
    C,
    {
      color?: Rainbow | 'black';
      size?: 'big' | 'small';
    }
  >;

/**
 * This is the type used in the type annotation for the component
 **/
type WrapperComponent = <C extends React.ElementType = 'span'>(
  props: WrapperProps<C>
) => React.ReactNode | null;

export const Wrapper: WrapperComponent = React.forwardRef(function Text<
  C extends React.ElementType = 'span',
>(
  { as, color, children, size = 'small' }: WrapperProps<C>,
  ref?: PolymorphicRef<C>
) {
  const style = {
    style: {
      color,
      fontSize: size === 'small' ? '10px' : '40px',
    },
  };
  return (
    <Text 
      as={as}  // 👈 This causes a typeerror in Text.  If I set it to as='div' it works. But I want wrapper to be able to specify a tag.  How do we make as a string?
      {...style} 
      ref={ref}
    >
      {children}
    </Text>
  );
});

Activity

  1. mrbinky3000 commented on Dec 12, 2023

    @mrbinky3000
    Author

    Well, I think I figured it out? Can you tell me if you agree with this approach? The code is the same as above, I just changed things after the comment labeled "Wrapper Component"

    // -----------------------------------------------------------------------------
    // Wrapper Component
    // -----------------------------------------------------------------------------
    
    type WrapperProps<C extends React.ElementType = 'span'> = {
      size?: 'big' | 'small';
      color?: string;
      children: React.ReactNode;
      as: C
    } & PolymorphicRef<C>
    
    type WrapperComponent = <C extends React.ElementType = 'span'>(
      props: WrapperProps<C>
    ) => React.ReactNode | null;
    
    export const Wrapper: WrapperComponent = React.forwardRef(function Text<
      C extends React.ElementType = 'span',
    >(
      { as, color, children, size = 'small' }: WrapperProps<C>,
      ref?: PolymorphicRef<C>
    ) {
      const style = {
        style: {
          color,
          fontSize: size === 'small' ? '10px' : '40px',
        },
      };
      return (
        <Text
          as={as} // 👈 This seems satisfied now
          {...style}
          ref={ref}
        >
          {children}
        </Text>
      );
    });
  2. mrbinky3000 commented on Dec 12, 2023

    @mrbinky3000
    Author

    Nope. That doesn't work. Wrapper will accept <Wrapper as="button" href="/test">Hello</Wrapper> and as we know, buttons don't have hrefs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions