Reputation: 749
I'm trying to use the Material UI 1.0 (beta) @withStyles annotation on a React component. The docs give a Javascript example (https://material-ui-1dab0.firebaseapp.com/customization/css-in-js), but it gives a compile error in Typescript. It actually emits ok and the app runs fine, so am just trying to rid the IDE of the error!
Here is my code:
@withStyles(styles)
class MyComponent extends React.Component<any, any> {
manager: any;
...
}
This gives error:
TS1238: Unable to resolve signature of class decorator when called as an expression.
Type 'ComponentClass<StyledComponentProps<{}>>' is not assignable to type 'typeof MyComponent'.
Type 'Component<StyledComponentProps<{}>, ComponentState>' is not assignable to type 'MyComponent'.
Property 'manager' is missing in type 'Component<StyledComponentProps<{}>, ComponentState>'.
I don't really understand this error. I can use the alternative non-annotated version like this:
const MyStyledComponent = withStyles(styles)(MyComponent);
and this compiles/runs without issues. I'd prefer to use annotations though, and I'd like to understand the error.
Any advice appreciated!
Upvotes: 1
Views: 1344
Reputation: 3952
I wasn't able to use @jcalz's type definition since it conflicts with the existing one, so I made a wrapper function.
Having classes
be required on WithStyles
also makes it a pain to use decorated components, so I make it optional instead.
// withStyles.ts
import { Theme } from 'material-ui/styles';
import _withStyles, {
ClassNameMap,
StyledComponentProps,
StyleRules,
StyleRulesCallback,
WithStylesOptions,
} from 'material-ui/styles/withStyles';
export interface WithStyles<ClassKey extends string = string> {
classes?: ClassNameMap<ClassKey>;
theme?: Theme;
}
// We need to fix the withStyles definition and we want to make WithStyles.classes optional,
// so we make our own.
export const withStyles = <ClassKey extends string>(
style: StyleRules<ClassKey> | StyleRulesCallback<ClassKey>,
options?: WithStylesOptions
) => <C extends React.ComponentType<P & WithStyles<ClassKey>>, P = {}>(component: C) => {
return (_withStyles as any)(style, options)(component) as C & React.ComponentType<P & StyledComponentProps<ClassKey>>;
};
To use it just import the new withStyles.ts
instead of the material-ui
one.
import { withStyles, WithStyles } from './withStyles';
@withStyles(styles)
export class MyClass extends React.Component<MyClassProps> {
render() {
// classes will always exist so use `!`.
const classes = this.props.classes!;
return (
<div className={classes.root} />
);
}
}
Upvotes: 1
Reputation: 329553
What this means is that the declared type of the @withStyles
decorator:
export default function withStyles<P = {}, ClassNames = {}>(
style: StyleRules | StyleRulesCallback,
options?: WithStylesOptions
): (
component: React.ComponentType<P & { classes: ClassNames; theme?: Theme }>
) => React.ComponentClass<P & StyledComponentProps<ClassNames>>;
doesn't actually meet the contract that TypeScript needs for class decorators, which is something more like:
declare function classDecorator(...args: any[]): <C> (c: C) => C;
The contract is pretty strict: it requires that the function output be a subtype of the input. This has led people to have problems before.
If everything is working fine at runtime and you just want to silence the type checker, there's always good old any
:
@(withStyles as any)(styles) // no error now
class MyComponent extends React.Component<any, any> {
manager: any;
...
}
Or, you could go ahead and try to re-declare the type of withStyles()
to be more friendly to decorators:
import { StyledComponentProps } from 'material-ui';
import { Theme } from 'material-ui/styles/createMuiTheme';
import { StyleRules, StyleRulesCallback, WithStylesOptions } from 'material-ui/styles/withStyles';
import { withStyles } from 'material-ui/styles'
declare module 'material-ui/styles' {
export function withStyles(
style: StyleRules | StyleRulesCallback,
options?: WithStylesOptions
): <C extends React.ComponentType<P & { classes: ClassNames; theme?: Theme }>, P = {}, ClassNames = {}> (
component: C
) => C & React.ComponentClass<P & StyledComponentProps<ClassNames>>;
}
@withStyles(styles) // no error now
class MyComponent extends React.Component<any, any> {
manager: any;
}
Now everything works because I ensure that the returned value is a subtype of the MyComponent
constructor. It's a lot of someone else's code to fix in your own project; I'd probably just use the any
solution myself.
Whether someone wants to contact the Material UI folks and suggest updating the definition is up to them. I don't consider myself knowledgeable enough on React et al. to understand if my altered declaration is appropriate.
Anyway, hope that helps; good luck!
Upvotes: 4