typescriptstyled-componentsreact-typescript

Prop Required for Styled Component Despite Being Specified with attr


I created a basic icon component that extends SVG from react-inlinesvg with styled-components to allow for custom styling props. In addition, an attr call specifies the src property to the SVG component, as different SVG files will have different styling implementation, therefore each one is its own component. Here's the relevant code for that:

import SVG from 'react-inlinesvg';
import { CSSProperties as _CSSProperties } from "react";
import styled, { Interpolation } from "styled-components";

// Remove undefined from CSSProperties, as all these constants are guaranteed to
// exist.
export type RequiredCSSProperties = Required<_CSSProperties>;

// Add a prop that allows for custom CSS to be added to a styled component instance without
// creating a new styled component.
export type WithCSSProp<T={}> = T & { $css?: Interpolation<Omit<T, "$css">> };

export type MenuIconStyleAttributes = {
    $color?: RequiredCSSProperties['stroke'] | null;
    $width?: RequiredCSSProperties['width'];
    $height?: RequiredCSSProperties['height'];
};

export default styled(SVG).attrs<WithCSSProp<MenuIconStyleAttributes>>(
    (props) => ({
        src: '/icons/menu.svg',
        $color: props.$color ?? null,
        $width: props.$width ?? '24px',
        $height: props.$height ?? 'auto',
        $css: props.$css ?? null
    })
)`
    width: ${({$width}) => $width};
    height: ${({$height}) => $height};

    > path {
        stroke: ${({$color, theme}) => $color ?? theme.textColor};
    }

    ${({$css}) => $css}
`;

My issue is that, when I'm using this component, the src property is being seen as required even though it's specified with a static value. Exact error:

No overload matches this call.
  Overload 1 of 2, '(props: PolymorphicComponentProps<"web", FastOmit<Substitute<Substitute<{ string?: string | number | undefined; stroke?: string | undefined; clipPath?: string | undefined; color?: string | undefined; cursor?: string | number | undefined; ... 481 more ...; uniquifyIDs?: boolean | undefined; }, { ...; }>, WithCSSProp<...>>, never>, void, void, {}, {}>): Element', gave the following error.
    Property 'src' is missing in type '{}' but required in type 'FastOmit<Substitute<FastOmit<Substitute<Substitute<{ string?: string | number | undefined; stroke?: string | undefined; clipPath?: string | undefined; color?: string | undefined; cursor?: string | number | undefined; direction?: string | ... 1 more ... | undefined; ... 480 more ...; uniquifyIDs?: boolean | undefined...'.
  Overload 2 of 2, '(props: FastOmit<Substitute<Substitute<{ string?: string | number | undefined; stroke?: string | undefined; clipPath?: string | undefined; color?: string | undefined; cursor?: string | number | undefined; direction?: string | ... 1 more ... | undefined; ... 480 more ...; uniquifyIDs?: boolean | undefined; }, { ...; }>, WithCSSProp<...>>, never>): ReactNode', gave the following error.
    Property 'src' is missing in type '{}' but required in type 'FastOmit<Substitute<Substitute<{ string?: string | number | undefined; stroke?: string | undefined; clipPath?: string | undefined; color?: string | undefined; cursor?: string | number | undefined; direction?: string | ... 1 more ... | undefined; ... 480 more ...; uniquifyIDs?: boolean | undefined; }, { ...; }>, With...'.

I'm able to override src to be optional, but the value would be dismissed anyway, so I really just want to get rid of that prop altogether. Is this possible?


Solution

  • I found an answer myself. I created the following wrapper function around the SVG component and styled this component rather than the SVG itself:

    import SVG from 'react-inlinesvg';
    import { ComponentProps } from 'react';
    
    function StaticSrcSVG(src: string) {
        return Object.assign(
            (props: Omit<ComponentProps<typeof SVG>, 'src'>) => <SVG src={src} {...props} />,
            { 
                displayName: `${src}(StaticSrcSVG)`
            }
        );
    }