I am trying the following:
const decorate = withStyles(({ palette }) => ({
root: {
}
}));
class MyComponent extends React.Component<WithStyles<'root'>> {
// What should be the type of the props?
constructor(props: WithStyles<'root'>) {
super(props);
// ... do things
}
render() {
const cl = this.props.classes!;
return <div className={cl.root}>hi</div>;
}
}
export default decorate<{}>(MyComponent);
However struggling to work out the right constructor props type
Error:(67, 29) TS2345:Argument of type 'typeof MyClass' is not assignable to parameter of type 'ComponentType<WithStyles<"root">>'.
Type 'typeof MyClass' is not assignable to type 'StatelessComponent<WithStyles<"root">>'.
Type 'typeof MyClass' provides no match for the signature '(props: WithStyles<"root"> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
The Typescript Guide in the docs doesn't provide an example to show how to type a constructor
| Tech | Version |
|--------------|---------|
| Material-UI | 1.0.0-beta.17 |
| React | 16.0.0 (using @types/react 16.0.18 |
Your snippet works fine for me... what version of TypeScript are you using?
Oh sorry forgot to add that to the env , 2.6-rc
Ah, it looks like this is due to TypeScript 2.6's --strictFunctionTypes combined with the fact that the actual signature of the constructor for React.Component is
constructor(props?: P, context?: any);
So you need to mark your props argument to the constructor as optional:
constructor(props?: WithStyles<'root'>) {
super(props)
// ... do things
}
I'm not sure why it's marked optional; afaik the React component's constructor will never be called with undefined props. Maybe it's because they want it to be optional to call
super(props)
If it's just that, and the concrete component will always be passed defined props, then it seems like the typing for React.ComponentClass is wrong; instead of
new (props?: P, context?: any): Component<P, ComponentState>;
it should be
new (props: P, context?: any): Component<P, ComponentState>;
Opened a PR for @types/react that would address this.
@pelotom Interesting, ok thanks for the diagnosis, will follow the progress of the PR too. Think you are right about the props, should always be set
Hi, I've struggled with it a lot of time. Here's the solution of using styles, themes and TS:
import * as React from 'react';
import { WithStyles, Button, withStyles } from 'material-ui';
import { StyleRules, Theme } from 'material-ui/styles';
const styleClasses = {
button: '',
};
const styles = (theme: Theme): StyleRules<keyof typeof styleClasses> => (
{ button: {
backgroundColor: theme.palette.primary[200]
}
}
);
interface MyButtonProps {
label: string;
}
class MyButton extends React.Component< MyButtonProps & WithStyles<keyof typeof styleClasses>> {
render () {
const classes = this.props.classes ? this.props.classes : styleClasses;
return (
<Button className={classes.button}>this.props.label</Button>
);
}
}
export default withStyles(styles)(MyButton);
This solution exports the ComponentClass with unnecessary "classes" proprty. Type checking works well - if you change styleClasses, the compiller will throw an error.
The main thing is to use & operator in types, not extending WithStyles or using it directly. If you see what does withStyles function you'll notice that it takes the component
The second thing is that you use styleClasses as default initializator in render() function .Without it you will need to write everywhere this.props.classes? this.props.classes.button : '' in order to avoid "the value is possible undefined' compiler errors.
Please, update TS section in documentation. It really takes time to understand how it works.
@snk5000 I think you're making it a little more complicated than it needs to be. Your example should work just as well with the following changes:
import * as React from 'react';
import { WithStyles, Button, withStyles } from 'material-ui';
import { StyleRules, Theme } from 'material-ui/styles';
-const styleClasses = {
- button: '',
-};
-const styles = (theme: Theme): StyleRules<keyof typeof styleClasses> => (
+const styles = (theme: Theme) => (
{ button: {
backgroundColor: theme.palette.primary[200]
}
}
);
interface MyButtonProps {
label: string;
}
-class MyButton extends React.Component< MyButtonProps & WithStyles<keyof typeof styleClasses>> {
+class MyButton extends React.Component< MyButtonProps & WithStyles<'button'>> {
render () {
- const classes = this.props.classes ? this.props.classes : styleClasses;
+ const { classes } = this.props;
return (
- <Button className={classes.button}>this.props.label</Button>
+ <Button className={classes.button}>{this.props.label}</Button> // minor bug fix...
);
}
}
export default withStyles(styles)(MyButton);
In particular I'm confused about the ternary expression you had inside render()... that really shouldn't be necessary because classes will always be defined in a component wrapped by withStyles(). And the typing for the last few beta versions has been consistent with this, so you shouldn't be getting "the value is possible undefined" errors.
@pelotom
Thank you for the reply. The statement in render function was from my previous version of code, I used StyledComponentProps before, and I had to check if props.classes exists.
With your approach there is no type checking of styles return type. I always forget to add new key here -> <& WithStyles<'button'> when I add it to styles function. That's why I decided to store keys in a separate variable.
It would be very good if you update this guide: https://material-ui-1dab0.firebaseapp.com/guides/typescript with these examples. I'm new in TS and it was very hard to understand TS usage with MUI.
With your approach there is no type checking of styles return type.
I'm not sure what you mean, the class keys are inferred.
I always forget to add new key here -> <& WithStyles<'button'> when I add it to styles function.
If you forget you will be reminded very quickly, because it will be a type error if you try to use it.
That's why I decided to store keys in a separate variable.
That's a fine approach, but you don't need this:
const styleClasses = {
button: '',
};
//...
class MyButton extends React.Component<MyButtonProps & WithStyles<keyof typeof styleClasses>> {
You can just write
const ClassKey = 'button';
//...
class MyButton extends React.Component<MyButtonProps & WithStyles<ClassKey>> {
It would be very good if you update this guide: https://material-ui-1dab0.firebaseapp.com/guides/typescript with these examples. I'm new in TS and it was very hard to understand TS usage with MUI.
I'm still not sure what this example shows that is not evinced by the guide. But the good news is you could update the guide just as easily as me :) Notice the button at the top of the page:

For the life of me can seem to find the correct solution to this problem. I keep getting an error that states typeof "my components name" is not assignable to type 'StatelessComponent', provides no match for the signature '(props: myprops & WithStyles<"" | "" |...>, and is not assignable to paramter of type"ComponentType I've tried everything in this issue, also used your typescript docs using a decorator and nothing seems to work. I still get the same error... Here is my code: Couldn't find anything on stackoverflow and this seems to be the closest issue I could find.const styles = (theme: Theme) => ({
selectField: {
width: "180px",
height: "48px",
},...
});
interface SelectProps {
id: string;
displayValue: any;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void | undefined;
blue?: boolean;
}
class OurSelectFields extends React.Component<SelectProps & WithStyles<"selectField" | "" ...>>{
render(){
const { classes } = this.props;
return( <MyComponent/>)
}
}
export const SelectField = withStyles(styles)(OurSelectFields);
@codepressd the code you have given compiles fine for me in both TypeScript 2.5.3 and 2.6.1, once I remove the ...s and replace <MyComponent /> (which you haven't defined) with <div />. I might be able to help you if you provide a minimal complete and self contained example which exhibits the error, along with the version of TypeScript you're using.
Thanks for getting back to me. I tried it again today with no luck... Here is my actual code.
const styles = (theme: Theme) => ({
selectField: {
width: "180px",
height: "48px",
},
redSelectFieldUnderline: {
"&:before": {
height: "1px",
backgroundColor: Colors.companyBrightRed(),
},
"& svg": {
fill: Colors.companyBrightRed(),
},
"&:after": {
backgroundColor: Colors.companyBrightRed(),
},
},
blueSelectFieldUnderline: {
"&:before": {
height: "1px",
backgroundColor: Colors.companyBlue,
},
"& svg": {
fill: Colors.companyBlue,
},
"&:after": {
backgroundColor: Colors.companyBlue,
},
},
redSelectHoverHack: {
width: "180px",
"&:hover": {
backgroundColor: "transparent",
borderBottom: `2px solid ${Colors.companyBrightRed()}`
},
"&:focus": {
outline: "none",
backgroundColor: "transparent",
borderBottom: `2px solid ${Colors.companyBrightRed()}`
}
},
blueSelectHoverHack: {
width: "180px",
"&:hover": {
backgroundColor: "transparent",
borderBottom: `2px solid ${Colors.companyBlue}`
},
"&:focus": {
outline: "none",
backgroundColor: "transparent",
borderBottom: `2px solid ${Colors.companyBlue}`
}
},
menuWrapStyles: {
top: "72px"
}
});
interface SelectProps {
id: string;
displayValue: any;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void | undefined;
blue?: boolean;
}
class OurSelectFields extends React.Component<SelectProps & WithStyles<"selectField" | "blueSelectField" | "redSelectField" | "menuWrapStyles" | "blueSelectHoverHack" | "redSelectHoverHack">, never>{
render() {
const { classes } = this.props;
return (
<TextField
id={this.props.id}
select
className={classes.selectField}
InputClassName={this.props.blue ? classes.blueSelectField : classes.redSelectField}
SelectProps={{
MenuProps: {
className: classes.menuWrapStyles,
}
}}
inputClassName={this.props.blue ? classes.blueSelectHoverHack : classes.redSelectHoverHack}
onChange={this.props.onChange}
value={this.props.displayValue}
>
{this.props.children}
</TextField>
)
}
};
export const SelectField = withStyles(styles)(OurSelectFields);
I'm using typescript 2.5.2 so I'll upgrade and see if that fixes it.
Thanks for your help
edit --- I still get the same error with typescript 2.5.3
@pelotom So I dug into this today and it has to do with classes being required in my interface...
// This works but have to pass an empty object into the component
interface mycomponentProps{
classes: object
}
// This fails
interface mycomponentProps{
classes?: object
}
I'm wondering if there is a cleaner way to make this work.
Thanks again for your help!!
@codepressd I'm not seeing the error you describe, but I'm seeing a different error because the class keys in styles don't match those in your WithStyles<...>.
@pelotom @snk5000
I think your default export should look like this :
- export default withStyles(styles)(MyButton);
+ export default withStyles(styles)<MyButtonProps>(MyButton);
Following that I have another issue, how do use that with another hoc. mainly how can it be integrated with composefrom recompose:
for example if we had to add another HOC, the following doesn't work :
const enhance = compose(withWidth(), withStyles(styles)<MyButtonProps>);
export default enhance(MyButton);
@stunaz this is not a valid TypeScript expression:
withStyles(styles)<MyButtonProps>
It should just be
withStyles(styles)
and type inference should be able to specialize the result appropriately. Beyond that, I can't really say what might be wrong because I don't have the complete code. If you still have problems, please post a complete, self-contained example as well as the error you're getting.
Hi @pelotom, here is my use case:
// Home.tsx
import * as React from 'react';
import Hello from './Hello';
interface IHomeProps {}
export default class Home extends React.Component<IHomeProps> {
public render() {
return <Hello name="Bob" />;
}
}
// hello.tsx
import * as React from 'react';
import { Grid } from 'material-ui';
import withWidth, { WithWidthProps } from 'material-ui/utils/withWidth';
import withStyles, { WithStyles } from 'material-ui/styles/withStyles';
import { compose } from 'redux';
import { Theme } from 'material-ui/styles';
const styles = (theme: Theme) => ({
root: {
backgroundColor: theme.palette.common.faintBlack
}
});
interface IHelloProps {
name?: string;
}
export class Hello extends React.Component<IHelloProps & WithWidthProps & WithStyles<'root'>> {
public static defaultProps = {
name: 'Alex'
};
public render() {
return (
<Grid
className={this.props.classes.root}
direction={this.props.width === 'sm' ? 'column' : 'row'}
>
<h1>Hello {this.props.name}!</h1>
</Grid>
);
}
}
const enhance = compose(withWidth(), withStyles(styles));
export default enhance(Hello);
The error I have on Home.tsxis the following
Type '{ name: "Bob"; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<WithWidthProps, ComponentState>> & Reado...'.
Type '{ name: "Bob"; }' is not assignable to type 'Readonly<WithWidthProps>'.
Property 'width' is missing in type '{ name: "Bob"; }'.
Taking compose out of the equation, since Redux typings are questionable, does this work any better?
export default withWidth()(withStyles(styles)(Hello))
same issue ...
event tried with
export default withWidth()(withStyles(styles)<IHelloProps>(Hello));
having the exact same error
@stunaz if you change the definition of withWidth to this:
export default function withWidth(
options?: WithWidthOptions
): <P>(
component: React.ComponentType<P & WithWidthProps>
) => React.ComponentClass<P & Partial<WithWidthProps>>;
does it work?
While overwriting the withWidth in my node_modules with the code above , I now get
Argument of type 'ComponentType<IHelloProps & StyledComponentProps<"root">>' is not assignable to parameter of type 'ComponentType<IHelloProps & StyledComponentProps<"root"> & WithWidthProps>'.
Type 'ComponentClass<IHelloProps & StyledComponentProps<"root">>' is not assignable to type 'ComponentType<IHelloProps & StyledComponentProps<"root"> & WithWidthProps>'.
Type 'ComponentClass<IHelloProps & StyledComponentProps<"root">>' is not assignable to type 'StatelessComponent<IHelloProps & StyledComponentProps<"root"> & WithWidthProps>'.
Type 'ComponentClass<IHelloProps & StyledComponentProps<"root">>' provides no match for the signature '(props: IHelloProps & StyledComponentProps<"root"> & WithWidthProps & { children?: ReactNode; }, context?: any): ReactElement<any>'.
Tried with another HOC different from withWidth, it worked, you on the right path, the issue seems to be with withWidth
@stunaz is that error when using compose still or with
export default withWidth()(withStyles(styles)(Hello))
?
All right, you last merged fixed it. i.e
A: the following works 馃挴
export default withWidth()(withStyles(styles)(Hello))
B: The following still don't works
const enhance = compose(withWidth(), withStyles(styles));
export default enhance(Hello);
throwing:
Type '{ name: "Bob"; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Partial<WithWidthProps>, ComponentState>...'.
So I guess the issue is now in redux / compose typing land
FWIW, even with perfect typings I think TypeScript in general has trouble inferring the types needed to compose 2 generic functions, so you probably need to pass explicit type parameters to compose... or, just do it like above 馃槃
I will stick with whatever works
Thanks for your help @pelotom , much appreciated
Closing since the original issue should be fixed by https://github.com/DefinitelyTyped/DefinitelyTyped/pull/20987.
Hey there, i fall in this issue many time and i have just found a solution that i thought, match your issue @stunaz.
First, i've created my decorate variable:
type IMyButtonProps = MyButtonProps & WithStyles<"button">;
const decorate = withStyles((theme: Theme) => ({
button: {
margin: theme.spacing.unit
}
}))
then i export like this :
export default compose(connect(mapStateToProps))(decorate<IMyButtonProps>(MyButton));
No need to had theme or classes property optional or of type any in your props.
So maybe for your case @stunaz
export default compose(withWidth(), connect(mapStateToProps))(decorate<IMyButtonProps>(MyButton));
/// If not working try this:
const MyButtonWithWidth = withWidth()(MyButton)
export default compose(connect(mapStateToProps))(decorate<IMyButtonProps>(MyButtonWithWidth));
I thought that the documentation about Typescript in material ui web site is clear but need a better explaination for class component with redux, compose, withStyles and withTheme all in one.
Hi @Vandell63 , I gave up on typescript, went back to flow. But I think the solutions in this thread are outdated. they were a lot of changements. you should file another issue.
Most helpful comment
Hi @pelotom, here is my use case:
The error I have on
Home.tsxis the following