Using Material-UI muiThemeable in Typescript

1.3k views Asked by At

I'm attempting to access the current theme colors in a custom React component. The example I'm using is from Material UI http://www.material-ui.com/#/customization/themes

I've tried a number of variations, but cannot get Typescript to compile my component. The error I get is... error TS2345: Argument of type 'typeof MainHeader' is not assignable to parameter of type 'Component<{}, {}>'.

import * as React from "react";
import muiThemeable from 'material-ui/styles/muiThemeable';

interface Properties  {
  title: string
}

class MainHeader extends React.Component<Properties, any> {

render() {
  return (
    <div className='hero' >
      <div className='logo' />
      <div className="bar" />

      <h1>{this.props.title}</h1>
    </div>
  );
  }
}

export default muiThemeable()(MainHeader);
1

There are 1 answers

7
Dan On

it seems to me the material-ui definition for muiThemeable function is not right ...

doing

export default muiThemeable()(MainHeader);

is the same as doing

export const themed = muiThemeable<MainHeader, {}, {}>()(MainHeader)

and doesn't compile because you omitted component property type and component property state

the compiler infers

<TComponent<P, S>,{}, {}>

which doesn't match the Function constraints

        export function muiThemeable<TComponent extends (React.Component<P, S>), P, S>()
            : (component: TComponent ) => TComponent;

... lets add the missing constrains

to satisfy the compiler , we should do

export default (props: Properties) => muiThemeable<MainHeader, Properties, any>()(new MainHeader(props));

but this is giving an instance to the function , when the F is expecting a class

if you later on do ...

// ...    
import Themeable from "./themeable";
let  props = { title: "hello!" };
ReactDOM.render(<Themeable {...props}/>, document.getElementById("main"));

it won't work

but if you change muiThemeable definition to:

 export function muiThemeable<TComponent extends (React.Component<P, S>), P, S>()
        : (component: Function ) => TComponent;

then you can use:

export default muiThemeable<MainHeader, Properties, any>()( MainHeader);

the tsc will generate errors

JSX element type 'Themeable' does not have any construct or call signatures

but it will transpile the right thing , and work

but that's not OK,

because:

  • it doesn't build
  • Function is NOT describing the right parameter type
  • neither TComponent describes what is returned,

it looks like its returning an instance when we need a class,... a type

finally:

this signature makes a bit more sense:

export function muiThemeable<TComponent extends (React.Component<P, S>), P, S>(): (component: new()=> TComponent ) => ( new() =>  TComponent);

but after looking at the source

export function muiThemeable<TComponent extends React.Component<P, S>, P extends MuiThemeProviderProps, S> (Component: new () => TComponent): React.StatelessComponent<P> 

And this could be a way to get away with re-writing or augmenting the definition.
Wrapping the function
... and perhaps using a decorator to reduce boilerplate and easier reading...

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import * as injectTapEventPlugin from "react-tap-event-plugin";

    // Needed for onTouchTap
    // http://stackoverflow.com/a/34015469/988941
    injectTapEventPlugin();


    import darkBaseTheme from "material-ui/styles/baseThemes/darkBaseTheme";
    import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
    import getMuiTheme from "material-ui/styles/getMuiTheme";
    import muiThemeable from "material-ui/styles/muiThemeable";

    import AppBar from "material-ui/AppBar";
    import MuiThemeProviderProps = __MaterialUI.Styles.MuiThemeProviderProps;

    function Themeable<TComponent extends React.Component<P, any>, P extends MuiThemeProviderProps> (Component: new () => TComponent): new() => TComponent {
        return muiThemeable<TComponent, P, any>()(Component as any) as any;
    }

    const themeable = <P, TFunction extends React.ComponentClass<P>>(target: TFunction): TFunction => {
        return Themeable(target as any) as any;
    };

    export interface MyBarProps extends __MaterialUI.AppBarProps, __MaterialUI.Styles.MuiThemeProviderProps {
        // ...
    }

    @themeable
    export class MyBar extends React.Component<MyBarProps, any> {

        constructor(props?: MyBarProps, context?: any) {
            super(props, context);
        }

        render() {
            if (!this.props.muiTheme) {
                throw new Error("muiTheme not Found");
            }
            return (
                <AppBar {...this.props} />
            );
        }
    }

    const darkTheme = getMuiTheme(darkBaseTheme);
    darkTheme.appBar.color = "red";

    const Main = () => (
        <MuiThemeProvider muiTheme={darkTheme}>
            <MyBar title="My AppBar" />
        </MuiThemeProvider>
    );

    ReactDOM.render(<Main></Main>,document.getElementById("root"));