Focus on naming is very important.
Styled components as the name suggests are for styling the native DOM elements or custom components.
attrs is for mentioning the attributes of that very same DOM element in the attrs constructor without the need to even mention it in actual component call.
What above line means is you can do
<Input placeholder="A small text input" />
with
const Input = styled.input.attrs(({ type }) => ({
type: type || "password"
}))`
align-items: center;
display: flex;
margin: 1.5vh 0;
`
See that component usage <Input .../> doesn't have type prop anywhere. It came from your attribute constructor function(static)
You couldn't have done this otherwise in your style rules because these are just the string literal of your CSS properties.
It saved you from writing type='password' in every usage of
<Input type='password' ... />
Bonus:
Now, that's a specific input component with type attribute of password. What if you wish to have a general input (styled component) with any value of type attribute?
Tada!
const Input = styled.input.attrs(({ type }) => ({
type: type || "password",
...
Your type is now dynamic i.e. it will take whatever input type prop you specify from your usage of component and render the input as that type (text, password, file etc. ) or if you skip the type prop it will pick up default of password. You can use as much conditional logic as you want up there.
Example:
<Input .../> // renders type="password"
<Input type="text" .../>
<Input type="email" .../>
Hope that answers your question.
Answer from HalfWebDev on Stack OverflowFocus on naming is very important.
Styled components as the name suggests are for styling the native DOM elements or custom components.
attrs is for mentioning the attributes of that very same DOM element in the attrs constructor without the need to even mention it in actual component call.
What above line means is you can do
<Input placeholder="A small text input" />
with
const Input = styled.input.attrs(({ type }) => ({
type: type || "password"
}))`
align-items: center;
display: flex;
margin: 1.5vh 0;
`
See that component usage <Input .../> doesn't have type prop anywhere. It came from your attribute constructor function(static)
You couldn't have done this otherwise in your style rules because these are just the string literal of your CSS properties.
It saved you from writing type='password' in every usage of
<Input type='password' ... />
Bonus:
Now, that's a specific input component with type attribute of password. What if you wish to have a general input (styled component) with any value of type attribute?
Tada!
const Input = styled.input.attrs(({ type }) => ({
type: type || "password",
...
Your type is now dynamic i.e. it will take whatever input type prop you specify from your usage of component and render the input as that type (text, password, file etc. ) or if you skip the type prop it will pick up default of password. You can use as much conditional logic as you want up there.
Example:
<Input .../> // renders type="password"
<Input type="text" .../>
<Input type="email" .../>
Hope that answers your question.
The goal of .attrs is so it can be passed down from your props. So you can use props inside your styled, and you can create placeholders, or change colors depending of the props etc...
For example :
const InputText = styled.input.attrs({
type: 'text',
placeholder: props => props.placeholder || 'Please fill',
})`
padding: 6px 12px;
`;
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("/media/images/image_file.png");" 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:

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

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;
`;