Required props that are defined as defaultProps
show as missing when using the component.
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
const Button = styled('button')<ButtonProps>`...`
Button.defaultProps = {
size: 'medium',
inverted: false,
raised: false,
}
export default Button
// Hello.tsx
import Button from './Button'
const Hello = () => <Button>Hello</Button>
Button is highlighted in red and displays this error message:
Type '{}' is missing the following properties from type
'Pick<Pick<Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement>, "form" | "style" | "title" | "key" | "autoFocus" |
"disabled" | "formAction" | ... 255 more ... | "onTransitionEndCapture"> &
{ ...; } & ButtonProps, "form" | ... 329 more ... | "raised"> &
Partial<...>, "form" | ... 329 ...': size, inverted, and 1 more.
Versions:
"@types/styled-components": "^4.1.9",
"@types/react": "^16.8.5",
"@types/react-dom": "^16.8.2",
"typescript": "^3.3.3333",
"styled-components": "^4.1.3"
"react": "^16.8.3",
"react-dom": "^16.8.3",
Edit While I'm personally ok with this behavior, it's not consistent with behavior from a normal React component (typescript supports defaultProps
).
I look around a bit, and it seems that this was fixed in @types/styled-components
, but then removed due to it introducing another bug.
Note from the type maintainer:
However, TS doesn't set type of newly set defaultProps (aka expando), so don't expect it would allow to skip required props. In order to modify the type, in the tests I added a simple helper function that sets defaultProps and returns modified type. This is meant to be an example only.
Here's that example:
// example of a simple helper that sets defaultProps and update the type
type WithDefaultProps<C, D> = C & { defaultProps: D };
function withDefaultProps<C, D>(component: C, defaultProps: D): WithDefaultProps<C, D> {
(component as WithDefaultProps<C, D>).defaultProps = defaultProps;
return component as WithDefaultProps<C, D>;
}
This helper does fix the problem (and is similar to @TitianCernicova-Dragomir's approach so you should consider marking his answer as correct if you accept this as a correct workaround).
/* setup */
interface Props {
requiredProp: number;
}
const defaultProps: Props = {
requiredProp: 1,
};
const Component = styled.div<Props>``;
const ComponentWithDefault = withDefaultProps(Component, defaultProps);
export { Component, ComponentWithDefault };
/* usage */
import { Component, ComponentWithDefault } from './component.tsx';
const a = <Component /> // Property requiredProp is missing...
const b = <ComponentWithDefault /> // no error
Requirements:
"@types/styled-components": "^4.1.12",
"typescript": "^3.2.4",
defaultProps
with styled-components did worked in the past — if you're interested in keeping that behavior, I think reverting to older version of @types/styled-components might help; I think it's 4.1.8 but I'm not sure.
Original answer
Perhaps not the answer you're looking for, but I find this behavior desireable.
If you have already defined certain properties in defaultProps
, wouldn't that make those properties optional?
I'd make defaulted props optional:
interface ButtonProps {
size?: 'small' | 'medium' | 'large';
inverted?: boolean;
raised?: boolean;
}
Or keep it intact, but wrap in Partial<>
since you're already provide default for all of them
const Button = styled('button')<Partial<ButtonProps>>`...`
Another things I usually do is to define defaultProps
first in a variable, so I can properly define their interface:
interface Props { ... }
const defaultProps: Props = { ... }
const Component = styled.div<Props>`...`
Component.defaultProps = defaultProps
The problem is that assigning defaultProps
will not change it's type which will remain the default of Partial<StyledComponentProps<"button", any, ButtonProps, never>>
. You can explicitly type the const
but typing a lot of types is never fun. A simpler approach is to use another HOC (everything can be solved with another HOC 😃) that mutates the type of defaultProps
to contain the default props you actually set on the component:
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
function withDefault<T extends { defaultProps?: Partial<TDefaults> }, TDefaults>(o: T, defaultProps: TDefaults): T & { defaultProps: TDefaults } {
o.defaultProps = defaultProps;
return o as any;
}
const Button = withDefault(styled('button') <ButtonProps>`...`, {
size: 'medium',
inverted: false,
raised: false,
})
export default Button
const Hello = () => <Button>Hello</Button> // OK now
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With