Edit:
Note that the incorrect constructor was generated by IntelliJ IDEA. This has now been fixed, see: https://youtrack.jetbrains.com/issue/WEB-35178
I am looking at using Material-UI in my app, but I'm having some trouble with the withStyles
styling solution in combination with TypeScript.
I'm trying to make my own little wrapper component, based off the Popper documentation.
The problem is, if I define a constructor explicitly the way I'm used to ((B)
in the code below), then this line:
export default withStyles(styles)(HelpComponent);
Give this error:
ERROR in [at-loader] ./src/main/ts/screen/keyword/HelpComponent.tsx:150:35
TS2345: Argument of type 'typeof HelpComponent' is not assignable to parameter of type 'ComponentType<never>'.
Type 'typeof HelpComponent' is not assignable to type 'StatelessComponent<never>'.
Type 'typeof HelpComponent' provides no match for the signature '(props: never, context?: any): ReactElement<any> | null'.
The only way I've been able to make this work is to omit the explicit constructor and define state as a field ((A)
in the code below).
Is there a way to declare a constructor the normal way when I use withStyles
?
I actually don't mind setting the state directly like this, it's a bit less boilerplate. Am I giving up anything by initialising the state outside the constructor this way?
Ultimately, I just don't understand that TypeScript error message - can anyone explain what it's trying to say?
import {
createStyles,
Paper,
Popper,
Theme,
WithStyles,
withStyles
} from '@material-ui/core';
import * as React from 'react';
import {ReactNode, SyntheticEvent} from 'react';
import {EventUtil} from "appUtil/EventUtil";
import {WarningSvg} from "component/svg-icon/WarningSvg";
let log = require("log4javascript").getLogger("HelpComponent");
export interface HelpComponentProps extends WithStyles<typeof styles> {
children:ReactNode;
}
export interface HelpComponentState {
open:boolean;
arrowRef?: HTMLElement;
}
class HelpComponent extends React.Component<HelpComponentProps, HelpComponentState> {
helpRef!: HTMLElement;
// (A)
state = {open: false, arrowRef: undefined};
// (B)
// constructor(props: HelpComponentProps, context: HelpComponentState){
// super(props, context);
// this.state = {open: false, arrowRef: undefined};
// }
handleClick = (event:SyntheticEvent<any>) => {
EventUtil.stopClick(event);
this.setState({open: !this.state.open,});
};
handleArrowRef = (node:HTMLElement) => {
this.setState({
arrowRef: node,
});
};
render(){
const {classes} = this.props;
return <span ref={(ref)=>{if(ref) this.helpRef = ref}}>
<WarningSvg onClick={this.handleClick}/>
<Popper id={"help-popper"} className={classes.popper} transition
open={this.state.open} anchorEl={this.helpRef}
modifiers={{arrow:{enabled:true, element: this.state.arrowRef}}}
>
<span className={classes.arrow} ref={this.handleArrowRef}/>
<Paper className={classes.paper}>{this.props.children}</Paper>
</Popper>
</span>;
}
}
const styles = (theme: Theme) => createStyles({
root: {
flexGrow: 1,
},
scrollContainer: {
height: 400,
overflow: 'auto',
marginBottom: theme.spacing.unit * 3,
},
scroll: {
position: 'relative',
width: '230%',
backgroundColor: theme.palette.background.paper,
height: '230%',
},
legend: {
marginTop: theme.spacing.unit * 2,
maxWidth: 300,
},
paper: {
maxWidth: 400,
overflow: 'auto',
},
select: {
width: 200,
},
popper: {
zIndex: 1,
'&[x-placement*="bottom"] $arrow': {
top: 0,
left: 0,
marginTop: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '0 1em 1em 1em',
borderColor: `transparent transparent ${theme.palette.common.white} transparent`,
},
},
'&[x-placement*="top"] $arrow': {
bottom: 0,
left: 0,
marginBottom: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '1em 1em 0 1em',
borderColor: `${theme.palette.common.white} transparent transparent transparent`,
},
},
'&[x-placement*="right"] $arrow': {
left: 0,
marginLeft: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 1em 1em 0',
borderColor: `transparent ${theme.palette.common.white} transparent transparent`,
},
},
'&[x-placement*="left"] $arrow': {
right: 0,
marginRight: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 0 1em 1em',
borderColor: `transparent transparent transparent ${theme.palette.common.white}`,
},
},
},
arrow: {
position: 'absolute',
fontSize: 7,
width: '3em',
height: '3em',
'&::before': {
content: '""',
margin: 'auto',
display: 'block',
width: 0,
height: 0,
borderStyle: 'solid',
},
},
});
export default withStyles(styles)(HelpComponent);
Versions:
If you look at the definition of React.ComponentType
:
type ComponentType<P = {}> = ComponentClass<P> | StatelessComponent<P>;
The alternative you are trying to use is ComponentClass
, which is defined as:
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
new (props: P, context?: any): Component<P, S>;
// ...
}
Notice that the context
parameter is optional (?
mark). The problem is that the context
parameter of your constructor is not optional, so HelpComponent
is not compatible with callers that expect to be able to omit the context
argument. If you make the parameter optional, the error should go away.
When TypeScript reports the error that typeof HelpComponent
is not assignable to the union type React.ComponentType
, it semi-arbitrarily picks one member of the union type to report a detailed error. Unfortunately, it didn't pick the one you intended, so the error message isn't very useful.
I actually don't mind setting the state directly like this, it's a bit less boilerplate. Am I giving up anything by initialising the state outside the constructor this way?
Yes, by overriding the state
property, you may be unintentionally changing its type to be something different from the state type argument that you passed to the React.Component
base class.
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