TypeScript
You can add static typing to JavaScript to improve developer productivity and code quality thanks to TypeScript.
Have a look at the Create React App with TypeScript example. A minimum version of TypeScript 2.8 is required.
Usage of withStyles
Using withStyles
in TypeScript can be a little tricky, but there are some utilities to make the experience as painless as possible.
Using createStyles
to defeat type widening
A frequent source of confusion is TypeScript's type widening, which causes this example not to work as expected:
const styles = {
root: {
display: 'flex',
flexDirection: 'column',
}
};
withStyles(styles);
// ^^^^^^
// Types of property 'flexDirection' are incompatible.
// Type 'string' is not assignable to type '"-moz-initial" | "inherit" | "initial" | "revert" | "unset" | "column" | "column-reverse" | "row"...'.
The problem is that the type of the flexDirection
property is inferred as string
, which is too arbitrary. To fix this, you can pass the styles object directly to withStyles
:
withStyles({
root: {
display: 'flex',
flexDirection: 'column',
},
});
However type widening rears its ugly head once more if you try to make the styles depend on the theme:
withStyles(({ palette, spacing }) => ({
root: {
display: 'flex',
flexDirection: 'column',
padding: spacing.unit,
backgroundColor: palette.background.default,
color: palette.primary.main,
},
}));
This is because TypeScript widens the return types of function expressions.
Because of this, we recommend using our createStyles
helper function to construct your style rules object:
// Non-dependent styles
const styles = createStyles({
root: {
display: 'flex',
flexDirection: 'column',
},
});
// Theme-dependent styles
const styles = ({ palette, spacing }: Theme) => createStyles({
root: {
display: 'flex',
flexDirection: 'column',
padding: spacing.unit,
backgroundColor: palette.background.default,
color: palette.primary.main,
},
});
createStyles
is just the identity function; it doesn't "do anything" at runtime, just helps guide type inference at compile time.
Augmenting your props using WithStyles
Since a component decorated with withStyles(styles)
gets a special classes
prop injected, you will want to define its props accordingly:
const styles = (theme: Theme) => createStyles({
root: { /* ... */ },
paper: { /* ... */ },
button: { /* ... */ },
});
interface Props {
// non-style props
foo: number;
bar: boolean;
// injected style props
classes: {
root: string;
paper: string;
button: string;
};
}
However this isn't very DRY because it requires you to maintain the class names ('root'
, 'paper'
, 'button'
, ...) in two different places. We provide a type operator WithStyles
to help with this, so that you can just write
import { WithStyles, createStyles } from '@material-ui/core';
const styles = (theme: Theme) => createStyles({
root: { /* ... */ },
paper: { /* ... */ },
button: { /* ... */ },
});
interface Props extends WithStyles<typeof styles> {
foo: number;
bar: boolean;
}
Decorating components
Applying withStyles(styles)
as a function works as expected:
const DecoratedSFC = withStyles(styles)(({ text, type, color, classes }: Props) => (
<Typography variant={type} color={color} classes={classes}>
{text}
</Typography>
));
const DecoratedClass = withStyles(styles)(
class extends React.Component<Props> {
render() {
const { text, type, color, classes } = this.props
return (
<Typography variant={type} color={color} classes={classes}>
{text}
</Typography>
);
}
}
);
Unfortunately due to a current limitation of TypeScript decorators, withStyles(styles)
can't be used as a decorator in TypeScript.
Customization of Theme
When adding custom properties to the Theme
, you may continue to use it in a strongly typed way by exploiting
Typescript's module augmentation.
The following example adds an appDrawer
property that is merged into the one exported by material-ui
:
import { Theme } from '@material-ui/core/styles/createMuiTheme';
import { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
declare module '@material-ui/core/styles/createMuiTheme' {
interface Theme {
appDrawer: {
width: React.CSSProperties['width']
breakpoint: Breakpoint
}
}
// allow configuration using `createMuiTheme`
interface ThemeOptions {
appDrawer?: {
width?: React.CSSProperties['width']
breakpoint?: Breakpoint
}
}
}
And a custom theme factory with additional defaulted options:
./styles/createMyTheme:
import createMuiTheme, { ThemeOptions } from '@material-ui/core/styles/createMuiTheme';
export default function createMyTheme(options: ThemeOptions) {
return createMuiTheme({
appDrawer: {
width: 225,
breakpoint: 'lg',
},
...options,
})
}
This could be used like:
import createMyTheme from './styles/createMyTheme';
const theme = createMyTheme({ appDrawer: { breakpoint: 'md' }});