How to type the use of Material-UI's `withStyles` decorator in the example?

Viewed 2960

I'm trying to figure out how to annotate the types of the following styles (I converted from plain JavaScript to TypeScript and am adding type annotations):

import * as React from 'react'
import { withStyles } from '@material-ui/core'

@withStyles(styles) // TYPE ERROR HERE
export default class App extends React.Component<{}, {}> {
    render(): JSX.Element {
        return (
            <div className="some-class"> Hello </div>
        )
    }
}

function styles() {
    return {
        // assume I need to use global styles, my actual project is more
        // complex and for some reasons I need to use global styles
        '@global': {
            '.some-class': {
                overflowX: 'hidden'
            },
        },
    }
}

How do we type this?

At first, it gives me an error like:

        Types of property 'overflowX' are incompatible.
          Type 'string' is not assignable to type '"scroll" | "hidden" | "visible" | "auto" | "clip" | "-moz-initial" | "inherit" | "initial" | "revert" | "unset" | undefined'.

So I changed it to the following which uses createStyles:

import * as React from 'react'
import { withStyles, createStyles } from '@material-ui/core'

@withStyles(styles) // TYPE ERROR HERE
export default class App extends React.Component<{}, {}> {
    render(): JSX.Element {
        return (
            <div className="some-class"> Hello </div>
        )
    }
}

function styles() {
    return createStyles({
        // assume I need to use global styles, my actual project is more
        // complex and for some reasons I need to use global styles
        '@global': {
            '.some-class': {
                overflowX: 'hidden'
            },
        },
    })
}

And now I get this error:

tmp.tsx:5:1 - error TS1238: Unable to resolve signature of class decorator when called as an expression.
  Type 'ComponentClass<Pick<{}, never> & StyledComponentProps<"@global">, any>' is not assignable to type 'void | typeof App'.
    Type 'ComponentClass<Pick<{}, never> & StyledComponentProps<"@global">, any>' is not assignable to type 'typeof App'.
      Type 'Component<Pick<{}, never> & StyledComponentProps<"@global">, any, any>' is not assignable to type 'App'.
        Types of property 'render' are incompatible.
          Type '() => ReactNode' is not assignable to type '() => Element'.
            Type 'ReactNode' is not assignable to type 'Element'.
              Type 'undefined' is not assignable to type 'Element'.

5 @withStyles(styles) // TYPE ERROR HERE
  ~~~~~~~~~~~~~~~~~~~

In my actual, more-complicated code, the error is similar, about not being able to match the Component type:

App.tsx:44:1 - error TS1238: Unable to resolve signature of class decorator when called as an expression.
  Type 'ComponentClass<Pick<AppProps, never> & StyledComponentProps<"@global">, any>' is not assignable to type 'void | typeof App'.
    Type 'ComponentClass<Pick<AppProps, never> & StyledComponentProps<"@global">, any>' is not assignable to type 'typeof App'.
      Type 'Component<Pick<AppProps, never> & StyledComponentProps<"@global">, any, any>' is not assignable to type 'App'.
        Property 'makeOnStatusWindowClick' is missing in type 'Component<Pick<AppProps, never> & StyledComponentProps<"@global">, any, any>'.

44 @withStyles(styles)
   ~~~~~~~~~~~~~~~~~~~

where makeOnStatusWindowClick is the very first method defined in my component class.

2 Answers

The Material UI docs that Daniel linked to say

Unfortunately due to a current limitation of TypeScript decorators, withStyles(styles) can't be used as a decorator in TypeScript.

so I was able to get it working by doing two things:

  • use createStyles on the return object of my styles function (and don't use an explicit return type definition on the function so that it uses the one generated by createStyles)
  • Use withStyles as not a decorator
  • Use WithStyles for your props, using the auto-detected type of your styles

The code looks like this:

import * as React from 'react'
import { withStyles, WithStyles, createStyles } from '@material-ui/core'

interface Props extends WithStyles<typeof styles> {}

class App extends React.Component<Props, {}> {
    render(): JSX.Element {
        return (
            <div className="some-class"> Hello </div>
        )
    }
}

export default withStyles(styles)(App)

function styles() /*: do not put a return type here */ {
    return createStyles({
        // assume I need to use global styles, my actual project is more
        // complex and for some reasons I need to use global styles
        '@global': {
            '.some-class': {
                overflowX: 'hidden'
            },
        },
    })
}

Fortunately they have an extensive documentation on typescript here https://material-ui.com/guides/typescript/

Also you are not mentioning the error you are getting, so it's tricky to know what's wrong

Probably your props type is missing classes field

Related