1

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);
Jayson
  • 43
  • 9

1 Answers1

1

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"));
Dan
  • 2,818
  • 23
  • 21
  • How exactly do you use this in TypeScript? Been trying to do the same thing as OP but can't seem to find the solution for this... Could you please post a full example on how one uses `muiThemeable` with TypeScript? – rfgamaral Jan 21 '17 at 14:39
  • @RicardoAmaral please see if [this](https://github.com/D10221/react-mobx-material-ui-rxjs-tedious-sqlserver-node-electron-typescript/blob/master/.vscode/chrome.cmd) helps, just uploaded it for your convenience :), buena suerte! – Dan Jan 22 '17 at 08:23
  • Was that link meant to point to that file exactly? Just cloned the whole project but couldn't find any reference to `muiThemeable`... Am I missing something? – rfgamaral Jan 22 '17 at 10:49
  • you are right, there isn't, but if look at [muiThemeable](https://github.com/fis-components/material-ui/blob/master/src/muiThemeable.jsx), you will see that just sets the props, from the context, that is pretty much what it does @inject from mobx. sorry about the link , I pasted the wrong url. – Dan Jan 22 '17 at 13:02
  • 1
    @RicardoAmaral, perhaps the edited answer helps a bit more... ? – Dan Jan 23 '17 at 05:42
  • More or less. I've read Material-UI documentation again (http://www.material-ui.com/#/customization/themes) and now I'm not sure that what the API does, it's not what I though it was. – rfgamaral Jan 23 '17 at 22:12
  • I mean, MUI has a theme system, one can change the theme on `MuiThemeProvider` and all MUI components will have the custom theme. I'm looking for my own components to have that custom theme too (or the default if none was specified) and I was looking at the `muiThemeable` function (that should be applied to all components I want themed) to give me a `this.props.muiTheme` property on my components so that I could read the custom theme properties. If I have to do stuff like `extends __MaterialUI.AppBarProps` on my components, that kinda sucks and it's too much boilerplate. – rfgamaral Jan 23 '17 at 22:14