Styled Components .attrs w/ TypeScript

Viewed 6035

I'm a little confused on how to use the .attrs() function with TypeScript. Say I have the following:

BottleBar.tsx:

interface IBottleComponentProps {
  fill?: boolean
}

const BottleComponent = styled.div.attrs<IBottleComponentProps>(({fill}) => ({
  style: {
    backgroundImage: `url("./media/images/${fill ? 'Bottle-Filled.png' : 'Bottle-Empty.png'")`
  }
}))<IBottleComponentProps`
  width: 20px;
  height: 20px;
`;

export default function BottleBar() {

  return (
    <Wrapper>
      <BottleComponent />
      <BottleComponent fill />
    </Wrapper>
  )
}

Now, the above code works, but I'm unsure why IBottleComponentProps is needed twice, both at the beginning and the end - And without it, I get the following:

Type '{ fill: boolean; }' is not assignable to type 'IntrinsicAttributes & Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "slot" | ... 253 more ... | "onTransitionEndCapture"> & { ...; }, "slot" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "slot" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }'.

Additionally, with the first code example, I get a browser log as such;

index.js:1 Warning: Received `true` for a non-boolean attribute `fill`.

It's honestly pretty confusing, and the Styled-Components documentation isn't very clear in this regard. A push in the right direction would be greatly appreciated.

2 Answers

Answer 1: Warning about fill

You need to choose a different name, maybe full, but not fill for your styled component. As fill is a standard attribute of some HTML elements. Also, at w3schools

Experiment:

If you declare fill to be string and pass it a string value, you can see a fill attribute added to you to div in HTML DOM, example:

<div
  fill="test"
  style="background-image: url(&quot;/media/images/image_file.png&quot;);" class="sc-AxiKw jDjxaQ">
</div>

fill is a property in SVGAttributes interface:

from node_modules/@types/react/index.d.ts:

interface SVGAttributes<T> extends AriaAttributes, DOMAttributes<T> {
  // Attributes which also defined in HTMLAttributes
  className?: string;
  id?: string;
  ...
  // SVG Specific attributes
  accentHeight?: number | string;
  ...
  fill?: string;
  ...
}

That's the reason of this warning:

Warning: Received `true` for a non-boolean attribute `fill`.
If you want to write it to the DOM, pass a string instead: fill="true" or fill={value.toString()}.

Answer 2: Why interface is required 2 times?

Below is the excerpt from related interface:

attrs<
      U,
      NewA extends Partial<StyledComponentPropsWithRef<C> & U> & {
          [others: string]: any;
      } = {}
  >(
      attrs: Attrs<StyledComponentPropsWithRef<C> & U, NewA, T>
  ): ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>;

U becomes : IBottleComponentProps which you pass C is HTML element or react component type

And the return type is ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>:

export interface ThemedStyledFunction<
    C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
    T extends object,
    O extends object = {},
    A extends keyof any = never

where C, T were already provided. You are providing O by passing IBottleComponentProps the 2nd time.

If you don't provide it your BottleComponent will look like below one with {} for the props i.e. no props:

without

If you provide, it will look like below one, with the right props.

with

In short, you have to provide the interface 2 times for now. You can provide any if you don't have your interface defined.

Looks like second type variable information is losing information from the first type.

Here's the definition of attr

export interface ThemedStyledFunction<
    C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
    T extends object,
    O extends object = {},
    A extends keyof any = never
> extends ThemedStyledFunctionBase<C, T, O, A> {
    // Fun thing: 'attrs' can also provide a polymorphic 'as' prop
    // My head already hurts enough so maybe later...
    attrs<
        U,
        NewA extends Partial<StyledComponentPropsWithRef<C> & U> & {
            [others: string]: any;
        } = {}
    >(
        attrs: Attrs<StyledComponentPropsWithRef<C> & U, NewA, T>
    ): ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>;

According to that NewA type variable should have necessary information from U type.

The result is however ThemedStyledFunction<"div", any, {}, never>

Ideally it would be similar to ThemedStyledFunction<"div", any, StyleProps & IBottleComponentProps, "style" | "fill">

type IBottleComponentProps = {
  fill?: boolean
}

type StyleProps = {
  style: {
    backgroundImage: string;
  }
}

const BottleComponent = styled.div.attrs<IBottleComponentProps, StyleProps & IBottleComponentProps>(({fill}) => ({
  style: {
    backgroundImage: `url("")`
  }
}))`
  width: 20px;
  height: 20px;
`;

Related