WithStyles<typeof styles> doesn't get along with with TS (no auto completion and I get type mismatch error).
New Material UI Core's WithStyles<> should be able to receive the the type of styles object somehow, for further auto-completion of classes from this.props.classes.
It works only if I hardcode the class names (i.e. WithStyles<'className'>), but it contradicts DRY principle.
I tried WithStyles<typeof styles>, WithStyles<keyof typeof styles>, but nothing works.
I don't get auto-completion for the classes and also type checking fails using withStyles:
const styles = (theme: Theme) => createStyles({
someClass:
});
interface Props extends WithStyles<typeof styles> {
prop: string
}
class SomeClass extends React.Component<Props> {... }
export default withStyles(styles)<Props>(SomeClass);
Tried also without createStyles or just withStyles(styles)(SomeClass), but nothing works.
Using WithStyles<keyof typeof styles>, I get no classes auto-completion, but types match.
Using WithStyles<typeof styles>, no auto completion and type mismatch error in withStyles:
...
Type SomeClass no match for signature
'(props: Props & Partial<WithTheme> & { classes: Record<string, string>;
string? any; T: string; StyleRulesCallback<infer, K>(): any ...}'
Written above.
I can proceed with development by hardcoding all classnames or giving up on type checking, but it's frustrating.
| Tech | Version |
|--------------|---------|
| Material-UI | v1.2.0 |
| React | v16.4.0 |
| Typescript| v2.9.2 |
Edit: Never mind all that nonsense.
Where do you expect autocompletion? When you call createStyles it can't possibly know what you want to define. And in the component it should know which classes are defined.
Could you provide a complete repro? Yours has currently syntax errors.
WithStyles<keyof ReturnType<typeof styles>>
@nmenke I did not need to use this construct and looking at the current definiton of WithStyles https://github.com/mui-org/material-ui/blob/73b0ca703d75085721e9e764b0fe950043682b99/packages/material-ui/src/styles/withStyles.d.ts#L41-L49
this should not be necessary.
@eps1lon This is extremely weird. It works in an empty project in my laptop.
I'll try tomorrow again at office and update.
Was talking about class names completion from this.props.class. Everything works as expected on a clean create-react-app-typescript project, MUI v1.2.0, same react and typescript versions.
Will check tomorrow if I missed something.
Okay, in my case, it doesn't work only when there are static members in the class.
For example, the following does not compile, complaining:
Type 'typeof MyPage' provides no match for the signature '(props: Partial<WithTheme> & { classes: Record<"blah" | "blah2" | "blah3", string>; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
````
```jsx
class MyPage extends React.PureComponent<WithStyles<typeof styles>> {
static getDerivedStateFromProps(props: WithStyles<typeof styles>) {...}
render() {
...
return ...;
}
}
export default withStyles(styles)(MyPage);
whereas if you take static stuff out, it works... So weird.
@pelotom Any ideas? 馃
BTW, it started happening when I bumped up versions of both @types/[email protected] and @material-ui/[email protected] to the latest. I'm using TS 2.9.2
Update:
I bumped down the version of @types/react to 16.4.6 and it worked. So I believe @types/react is the problem. However, I do not know why it could cause this problem, given the only change is this:
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27417/
@franklixuefei can you reproduce this in a codesandbox? I'm not able to reproduce here: https://codesandbox.io/s/2m0j23klp
@pelotom I've been trying to repro this in codesandbox, but with no luck. It seems like codesandbox is using a version of @types/react which is not latest, because even if I removed the @types/react from packages.json in codesandbox (both dependencies and devDependencies), the code still compiled. So this means codesandbox must have some sort of built-in react type definition somewhere.
However, if you set up your own environment in VSCode, with [email protected], @types/[email protected], the problem will start to occur. I've tried removing node_modules and re-installing all dependencies, but still same problem.
After some more digging, I think this is related to the latest change in @types/react (16.4.6 -> 16.4.7), which passed ComponentState type parameter to StaticLifecycle, which seems to have caused the problem. If I comment out the second parameter in my static getDerivedStateFromProps(), the problem will be gone. However, I need that parameter.
Problematic code:
import * as React from "react";
import { Theme } from "@material-ui/core/styles/createMuiTheme";
import withStyles, {
CSSProperties,
WithStyles
} from "@material-ui/core/styles/withStyles";
interface State {
test: string;
test2: number;
}
const styles = (theme: Theme) => ({
style1: {
position: "relative"
} as CSSProperties
});
class Test extends React.PureComponent<WithStyles<typeof styles>, State> {
constructor(props: WithStyles<typeof styles>) {
super(props);
this.state = {
test: "test",
test2: 2
};
}
static getDerivedStateFromProps(
props: WithStyles<typeof styles>,
state: State // <-- If you comment this out, the error will disappear
): Partial<State> | null {
return {
test: '123'
};
}
render() {
return null;
}
}
export default withStyles(styles)(Test); // <-- Error: Type 'typeof Test' provides no match for the signature '(props: Partial<WithTheme> & { classes: Record<"style1", string>; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
Really not sure why this is happening...
For now I'll just bump down to @types/[email protected]
Any ideas will be appreciated!
After some more digging... I found the root cause in @types/react.
According to a previous commit - DefinitelyTyped/DefinitelyTyped#27417, ComponentState, which is {}, has been explicitly passed to StaticLifecycle as the state type parameter from ComponentClass. However, in our withStyles.d.ts, we have
export default function withStyles<ClassKey extends string>(
style: StyleRulesCallback<ClassKey> | StyleRules<ClassKey>,
options?: WithStylesOptions<ClassKey>,
): {
<P extends ConsistentWith<P, StyledComponentProps<ClassKey>>>(
component: React.ComponentType<P & WithStyles<ClassKey>>,
): React.ComponentType<Overwrite<P, StyledComponentProps<ClassKey>>>;
};
where we don't specify a state type in React.ComponentType. This results in the state type defaulting to {}, which further constrains that the type of the state parameter of static getDerivedStateFromProps(props, state) to be {}, which makes the compiler complain, however, about something not readable.
As a proof, if I change the type of prevState from State to {}, the code compiles.
import * as React from "react";
import { Theme } from "@material-ui/core/styles/createMuiTheme";
import withStyles, {
CSSProperties,
WithStyles
} from "@material-ui/core/styles/withStyles";
interface State {
test: string;
test2: number;
}
const styles = (theme: Theme) => ({
style1: {
position: "relative"
} as CSSProperties
});
class Test extends React.PureComponent<WithStyles<typeof styles>, State> {
constructor(props: WithStyles<typeof styles>) {
super(props);
this.state = {
test: "test",
test2: 2
};
}
static getDerivedStateFromProps(
props: WithStyles<typeof styles>,
state: {} // <-- changed from State to {}, and the code compiles
): Partial<State> | null {
return {
test: '123'
};
}
render() {
return null;
}
}
export default withStyles(styles)(Test);
@franklixuefei I see, so the definition of withStyles probably needs to change to track the state parameter; something like
export default function withStyles<ClassKey extends string>(
style: StyleRulesCallback<ClassKey> | StyleRules<ClassKey>,
options?: WithStylesOptions<ClassKey>,
): {
<P extends ConsistentWith<P, StyledComponentProps<ClassKey>>, S>(
component: React.ComponentType<P & WithStyles<ClassKey>, S>,
): React.ComponentType<Overwrite<P, StyledComponentProps<ClassKey>>, S>;
};
@pelotom I tried exactly that, but it will break the existing code, because S here does not have a default type.
I just filed a PR against @types/react. https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27914
There's only one change there - I changed {} to any.
@pelotom There is one more important reason why only changing withStyles.d.ts is not enough, because ComponentType does not accept a second type parameter unfortunately. (which I think is reasonable because ComponentType is a union of ComponentClass and StatelessComponent, and a StatelessComponent does not have a state by design)
So that's why I decided to just change {} to any in @types/react, because it is easy to reason about and is a very simple fix.
@franklixuefei I see, makes sense. Good luck with the PR!
FYI - DefinitelyTyped/DefinitelyTyped#27914 is now checked in. At least my issue is now resolved. 馃槃
@franklixuefei does that mean this can be closed?
@pelotom I believe so. I believe this issue is also related to #11693, which should have already been resolved because #12456 is checked in.
Great, thanks!
Most helpful comment
@franklixuefei I see, makes sense. Good luck with the PR!