polymorphic-react-component
polymorphic-react-component copied to clipboard
How do I wrap my Polymorphic Component?
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>
);
});
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>
);
});
Nope. That doesn't work. Wrapper will accept <Wrapper as="button" href="/test">Hello</Wrapper>
and as we know, buttons don't have hrefs.