Integrating material-ui@next with your React + TypeScript project

Assumptions

I assume that you have installed:

  • React v16
  • material-ui@1.0.0-beta.16
  • redux (optional)

and all the other necessary packages such as the corresponding @types/ typings for your libraries

React Types

Stateful React components have its own prop and state types. This can be defined in the component itself.

interface OwnProps {}
interface OwnState {}
class Button extends React.Component<OwnProps, OwnState> { 
constructor(props: OwnProps) {
...
}
  render() { 
return (
<button {...this.props}>
Click Me
</button>
)
}
}

These types are required so that TypeScript knows that the component has access to the props being passed in and the internal state using this.state

Injected Types with Redux

If you’re passing props from a Redux store into the component, you would need to tell the component what kind of props you are injecting into the component. This can be done with intersection types by using the operator &

Note that these variables are NOT passed in from the parent, therefore you should state in the component what props are being injected so that it’s clearer that it’s coming from the store.

// store.ts 
export namespace Store {
export type itemIds: number[]
export All {
itemIds: itemIds
}
}
// MyComponent.tsx 
import { connect } from 'react-redux'
import { Store } from './store'
interface OwnProps {} 
interface OwnState {}
interface InjectedPropsFromStore { 
itemIds: Store.itemIds
}
class MyComponent extends React.Component<OwnProps & InjectedPropsFromStore, OwnState> { 
...
}
const mapStateToProps = (store: Store.All) => { 
return {
itemIds: store.itemIds
}
}
export default connect(mapStateToProps)(MyComponent)

In this way, there is a clear distinction between props that are being passed in, {}, and the props that are being injected into the component from the store, {itemIds: Store.All}

Then (material-ui@1.0.0-beta.12)

In beta.12, you would just need to use StyleSheet and StyledComponentProps to use your JSS styles.

There was one slight problem: the classes prop weren't being recognized by TypeScript because they were generic

import { withStyles, StyleSheet } from 'material-ui/styles' 
import { StyledComponentProps } from 'material-ui/'
interface OwnProps {}
const style: StyleSheet = { 
heading: {
color: 'red',
fontFamily: 'monospace'
}
}
class MyComponent extends React.Component<OwnProps & StyledComponentProps, {}> {
render() {
const { classes } = this.props
return (
<h1 className={(!!classes) ? classes.heading : ''}>
Hello, world.
</h1> )
}
}
export default withStyles(style)(MyComponent)

Notice the {(!!classes) ? classes.heading : ''}, this is done because the types weren't defined properly and the component has no idea what kind of keys exist inside the this.props.classes

Now (material-ui@1.0.0-beta.16)

With the latest version of material-ui (as of writing), there are new types for using JSS styles in your components.

In beta.16, you can strictly define the keys in the classes prop. This can be done using the new type WithStyles<T> where T is the union type of the name of the keys that can exist inside classes.

No more confusing checking if the key exists inside this.props.classes, and no more usage of StyledComponentProps

interface OwnProps { 
externalProp: any
}
type ComponentClassNames = 
| 'heading'
| 'root'
const style: StyleRules<ComponentClassNames> = { 
heading: {
color: 'blue',
},
root: {
width: 300,
height: 200,
border: '2px solid #000',
},
}
class MyComponent extends React.Component<OwnProps & WithStyles<ComponentClassNames>, {}> {
render() {
const { classes } = this.props
return (
<div className={classes.root}>
<h1 className={classes.heading}>
Hello, world!
</h1>
</div>
)
}
}

export default withStyles(style)<OwnProps>(MyComponent)

Notice that now we are defining the union type ComponentClassNames that has all the names of the keys inside the JSS stylesheet style

There is one more catch with withStyles:

export default withStyles(style)<OwnProps>(MyComponent)

Notice the <OwnProps> just after the first function call. This is done as the component does not know which props are props that are explicitly being passed into the component from the parent. If you omit this type declaration, you will encounter an error when you use the component.

Redux + WithStyles

If you are like me and using redux and material-ui, you can combine the above.

// store.ts 
export namespace Store {
export type itemIds: number[]
export All {
itemIds: itemIds
}
}
// MyComponent.tsx 
import { connect } from 'react-redux'
import { withStyles, StyleRules, WithStyles } from 'material-ui'
import { Store } from './store'
interface OwnProps { message: any } 
interface InjectedProps { itemIds: Store.itemIds }
type ComponentClassNames = 
| "heading"
| "root"
const style: StyleRules<ComponentClassNames> = { 
heading: {
color: 'blue',
},
root: {
width: 300,
height: 200,
border: '2px solid #000',
},
}
class MyComponent extends React.Component<OwnProps & InjectedProps & WithStyles<ComponentClassNames>, {}> { 
render() {
const { classes, message = '' } = this.props
return (
<div className={classes.root}>
<h1 className={classes.heading}>
{(message)
? message
: 'Hello World!'
}
</h1>
</div>
)
}
}
const connectedComponent = connect(mapStateToProps)(MyComponent) 
const applyStyles = withStyles(style)<OwnProps>(connectedComponent) 
export default applyStyles
// In some other component... 
import MyComponent from './MyComponent'
... 
render() {
return (
<MyComponent message={'This is a custom message!'} />
)
}
...

And with that, you’re all ready to build a React application with TypeScript, Redux, and material-ui with confidence.

TypeScript can be daunting but it makes an application more predictable and more logically structured rather than hunting down props like: “What kind of props does this component recieve?” and “What props are injected to the component? Better look at mapStateToProps”


About me:

  • Dynamic Test Automation Engineer at trivago
  • Software Engineering student at the University of Duisburg-Essen, Germany

Check out my portfolio here!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.