简体   繁体   中英

How to extend styled-components props and pass to children?

I am scratching my head to understand how to extend the styled components so I can pass a couple of props to its children while making typescript lint properly. This is what I have:

// ButtonBase.tsx

export const ButtonBase = styled.button`
  background: var(--primary);
  border: none;
`;

// IconButton.tsx

type Props = {
    iconName: IconProps['name']
};

export const IconButton = forwardRef<HTMLButtonElement, Props>(({
    iconName,
    ...props
}, ref) => (
    <ButtonBase
        {...props}
        ref={ref}
    >
        <Icon name={iconName}/>
    </ButtonBase>
));

But the problem with this is it doesn't allow styled-components props like as , theme , etc. I have tried the following setups to varying degrees of outcomes:

Started here, forwarding the props the usual way to no success.

type Props = {
    iconName: IconProps['name']
};

type IconButtonType = FC<StyledComponentProps<typeof ButtonBase, any, Props, any>>;

export const IconButton: IconButtonType = ({iconName, ...props}) => (...);

The ref was not available, so added that.

type IconButtonType = ForwardRefExoticComponent<PropsWithoutRef<StyledComponentProps<typeof ButtonBase, any, Props, any>> & RefAttributes<HTMLButtonElement>>;

Used StyledComponentProps but for some reason, the TS doesn't trigger if I miss iconName .

type Props = StyledComponentProps<'button', any, {
    iconName: IconProps['name']
}, any>;

Tried separating the iconName out, and now TS validates iconName but doesn't suggest anything about button.

type Props = StyledComponentProps<'button', any, any, any> & {
    iconName: IconProps['name']
};

I tried using the button attributes but styled props are then unavailable.

type Props = HTMLAttributes<HTMLButtonElement> & {
    iconName: IconProps['name']
};

This is what I have right now, but it throws an error for as prop, "a" is not assignable to type IntrinsicAttributes

type StyledProps<C extends Element, A extends Element = C> = React.ComponentPropsWithRef<C> & {
    as?: A
}; // I copied this from styled-components.d.ts

type ButtonBaseProps = StyledProps<'button'>;

type Props = ButtonBaseProps & {
    iconName: IconProps['name']
};

The goal here is to make IconButton work just like a styled-component taking as , theme , ref , and forwardedAs , and at the same time make TS suggest and validate props on the consumer side.

I think I am missing the composition knowledge to do this. I tried searching the inte.net but I believe the terms of my search are off the point, I get different results about composition but none of them really explain how to keep the styled-components props while adding custom props for nested children. Maybe it is not meant to be composed? In my previous project, I exported a Button with an icon variant and let the consumer do the composition. This is new to me, so any direction would be greatly appreciated. TIA!

Used StyledComponentProps but for some reason, the TS doesn't trigger if I miss iconName .

You were almost there: the 4th type argument any refers to this :

// The props that are made optional by .attrs
A extends keyof any,

...so if you pass any as type argument, you are basically saying that all props are optional, hence the absence of error if you do not provide iconName .

In your case, you do not use .attrs method, so there are no optional'ed props, hence you can use never type instead.


Then, as you already realized, we still need to add the as polymorphic prop (actually, there is also forwardedAs ), so we now have:

type Props = StyledComponentProps<'button', any, {
    iconName: IconProps['name']
}, never // `never` optional'ed attributes from .attrs
> & {
    // Add `as` and `forwardedAs` polymorphic props
    as?: string | React.ComponentType<any> | undefined;
    forwardedAs?: string | React.ComponentType<any> | undefined;
}

() => (<>
    <IconButton as={"button"} iconName="foo" />{/* Okay */}
    <IconButton />{/* Error: Property 'iconName' is missing in type '{}' but required in type... */}
    {/*~~~~~~~~*/}
    <IconButton iconName="foo" type="bar" />{/* Error: Type '"bar"' is not assignable to type '"button" | "submit" | "reset" | undefined'. */}
    {/*                        ~~~~*/}
</>);

Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM