@Igorbek @probablyup originally posted this in https://github.com/styled-components/styled-components/issues/1959 and was told to post here.
Hi, it's unclear how attrs should be used with TypeScript, I'm not sure if there's just a bug in the types. Here's what we've got so far, this is the only way to make the component definition work as expected, but then the component usage fails with the error [ts] Type 'string' is not assignable to type 'never'..
import styled from '../utils/mural-styled-components';
interface AdditionalProps {
readonly size: string;
}
interface ChangedProps {
readonly type: string;
readonly margin: string;
readonly padding: string;
}
const WeirdComponent = styled.input.attrs<AdditionalProps, ChangedProps>({
// we can define static props
type: 'password',
// or we can define dynamic ones
margin: (props) => props.size || '1em',
padding: (props) => props.size || '1em',
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed props */
margin: ${(props) => props.margin};
padding: ${(props) => props.padding};
`;
import React from 'react';
export const X = () => (
<WeirdComponent size="massive"/>
); // [ts] Type 'string' is not assignable to type 'never'.
## System:
- OS: Linux 4.15 Linux Mint 19 (Tara)
- CPU: x64 Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz
- Memory: 7.84 GB / 15.60 GB
- Container: Yes
- Shell: 4.4.19 - /bin/bash
## Binaries:
- Node: 10.7.0 - ~/.nvm/versions/node/v10.7.0/bin/node
- npm: 6.4.0 - ~/github/mural/node_modules/.bin/npm
## npmPackages:
- styled-components: ^3.3.0 => 3.4.4
Seems like size property is conflicting with React InputHTMLAttributes<T>
interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
...
pattern?: string;
placeholder?: string;
readOnly?: boolean;
required?: boolean;
size?: number; //already defined
src?: string;
step?: number | string;
...
}
And attrs calculates its attributes using diff between additional props & react element attributes.
export interface ThemedStyledFunction<P, T, O = P> {
(
strings: TemplateStringsArray,
...interpolations: Interpolation<ThemedStyledProps<P, T>>[]
): StyledComponentClass<P, T, O>
<U>(
strings: TemplateStringsArray,
...interpolations: Interpolation<ThemedStyledProps<P & U, T>>[]
): StyledComponentClass<P & U, T, O & U>
attrs<U, A extends Partial<P & U> = {}>(
attrs: Attrs<P & U, A, T>
): ThemedStyledFunction<DiffBetween<A, P & U>, T, DiffBetween<A, O & U>>
}
So below code works.
interface AdditionalProps2 {
size22: string;
// size: string; // not works if this line uncommented.
}
const WeirdComponent = styled.input.attrs<AdditionalProps2>({ })`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const X = () => (
<WeirdComponent size22={"massive"} size={324} defaultValue='dsdsd'/>
);
Ah great spot, thanks @Epikem 👍
Hey, @antoinerousseau, does this help? I wish this example had been around when I was first looking!
Custom props are props which are not valid HTML attributes of the element being styled.
Choose prop names which will clearly never conflict with any default attributes of the tag being used. eg. isFixed is not among the valid DIV attributes.
interface Props {
isFixed?: boolean
}
const Box = styled.div`
position: ${({ isFixed = false }: Props) => (isFixed ? 'fixed' : 'absolute')};
top: 0;
`
export default () => (
<div>
<Box isFixed={true}>I'm a box</Box>
</div>
)
In this case type is among the valid attributes of the button element, and isFixed is not!
I've found it useful to use separate interface statements for attrs and props.
interface Props {
isFixed?: boolean
}
interface Attrs {
type?: string
}
const Button = styled.button.attrs(({ type = 'button' }: Attrs) => ({
type,
}))`
position: ${({ isFixed = false }: Props) => (isFixed ? 'fixed' : 'absolute')};
`
export default () => (
<div>
<Button isFixed={true} type="submit">
I'm a button with type "submit" instead of default type of "button"
</Button>
</div>
)
@beausmith thanks!
Thanks! Nice catch.
Sadly I got stuck on a different error using typescript 3.3.3333. I copied the code from the example:
import styled from 'styled-components';
import React from 'react';
interface AdditionalProps2 {
size22: string;
// size: string; // not works if this line uncommented.
}
const WeirdComponent = styled.input.attrs<AdditionalProps2>({})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const X = () => <WeirdComponent size22={'massive'} size={324} defaultValue="dsdsd" />;
and typescript returned the following error:
error TS2322: Type '{ size22: string; size: number; defaultValue: string; }' is not assignable to type 'IntrinsicAttributes & { max?: string | number; required?: boolean; disabled?: boolean; hidden?: boolean; dir?: string; form?: string; slot?: string; style?: CSSProperties; title?: string; pattern?: string; ... 273 more ...; readOnly?: boolean; } & { ...; } & { ...; }'.
Property 'size22' does not exist on type 'IntrinsicAttributes & { max?: string | number; required?: boolean; disabled?: boolean; hidden?: boolean; dir?: string; form?: string; slot?: string; style?: CSSProperties; title?: string; pattern?: string; ... 273 more ...; readOnly?: boolean; } & { ...; } & { ...; }'.
16 export const X = () => <WeirdComponent size22={'massive'} size={324} defaultValue="dsdsd" />;
Not being a typescript expert I have no clue yet what might fix this :-/
@nielskrijger - the reason that you are getting this error using the size attribute is because it is already an intrinsic attribute of the input html element. You need to use a different attribute name.
@nielskrijger
I solved it temporarily.
import styled from 'styled-components';
import React from 'react';
interface AdditionalProps2 {
size22: string;
// size: string; // not works if this line uncommented.
}
const WeirdComponent = styled.input.attrs<AdditionalProps2>({})<AdditionalProps2>`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const X = () => <WeirdComponent size22={'massive'} size={324} defaultValue="dsdsd" />;
To be honest I find myself more comfortable wrapping external dependencies in an API of my own. In the case of styled components, I came up with this typed wrapper that makes my life easier:
interface StyleProps<T> extends Partial<Attr> {
element: keyof JSX.IntrinsicElements | React.ComponentType<any>
css: (props: T) => string
className?: (props: T) => string
}
const style = <T extends {}>({ element, css, ...attrs }: StyleProps<T>) => {
return styled(element).attrs<T>(attrs)<T>`${props => css(props)}`
}
You could add overloads to support string literal versions of css or className, but for now I find this easier.
attrs is going to be deprecated in v5 according to my VSCode JSDoc preview:

What I've found to work much better is using a function argument to the regular div/button/etc funciton as in this tweet:
https://twitter.com/siddharthkp/status/1191690477789208578?s=20
That way, typing works easily and you just pass a generic
e.g.
export const Container = styled.div<{ rotationSeed: number }>(
({ rotationSeed, theme }) => {
const rotationAmount = randomRotation(rotationSeed);
return `
display: flex;
flex-direction: column;
transition: transform 0.3s ease, box-shadow 0.3s ease;
transform: rotateZ(${rotationAmount}deg);
&:hover {
transform: rotateZ(-${rotationAmount}deg) scale(1.1);
}
`;
},
);
It’s not deprecated, just a particular syntax is.
On Fri, Nov 8, 2019 at 9:22 AM James Mulholland notifications@github.com
wrote:
attrs is going to be deprecated in v5 according to my VSCode JSDoc preview:
[image: image]
https://user-images.githubusercontent.com/20966690/68483053-b6664f00-0232-11ea-9712-780f1b2605f3.pngWhat I've found to work much better is using a function argument to the
regular div/button/etc funciton as in this tweet:https://twitter.com/siddharthkp/status/1191690477789208578?s=20
That way, typing works easily and you just pass a generic
e.g.
export const Container = styled.div.attrs<{ rotationSeed: number }>(
({ rotationSeed, theme }) => {
const rotationAmount = randomRotation(rotationSeed);
returndisplay: flex; flex-direction: column; transition: transform 0.3s ease, box-shadow 0.3s ease; transform: rotateZ(${rotationAmount}deg); &:hover { transform: rotateZ(-${rotationAmount}deg) scale(1.1); };
},
);—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28597?email_source=notifications&email_token=AAELFVVPQM4NAJX3HLS3T6LQSVYZNA5CNFSM4FS4ZGZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDSIAAA#issuecomment-551845888,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAELFVRYUCQHZWHCIVQNBL3QSVYZNANCNFSM4FS4ZGZQ
.
Oh, interesting. Can you elaborate?
Sure, the ability to supply an attrs object that has functions consuming
props as keys is what was deprecated in v4 and removed in v5. The
recommended variant is to supply an attrs function and return an object
containing the desired overrides instead.
So basically do a function that receives props to return an object, rather
than an object that has one or more inner functions that consume props.
On Fri, Nov 8, 2019 at 9:48 AM James Mulholland notifications@github.com
wrote:
Oh, interesting. Can you elaborate?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28597?email_source=notifications&email_token=AAELFVQAOKWEOCFIRL4V4TTQSV33DA5CNFSM4FS4ZGZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDSKOMY#issuecomment-551855923,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAELFVUZMFQVXPTCF2QXRLDQSV33DANCNFSM4FS4ZGZQ
.
Most helpful comment
Seems like
sizeproperty is conflicting with ReactInputHTMLAttributes<T>And
attrscalculates its attributes using diff between additional props & react element attributes.So below code works.