跳转到内容

Migrating from JSS (optional)

This guide explains how to migrate from JSS to Emotion when updating from Material UI v4 to v5.

Material UI v5 migration

  1. Getting started
  2. Breaking changes part one: style and theme
  3. Breaking changes part two: components
  4. Migrating from JSS 👈 you are here
  5. Troubleshooting

Migrating from JSS to Emotion

One of the biggest changes in v5 is the replacement of JSS for Emotion (or styled-components as an alternative) as a default styling solution .

Note that you may continue to use JSS for adding overrides for the components (e.g. makeStyles, withStyles) even after migrating to v5. Then, if at any point you want to move over to the new styling engine, you can refactor your components progressively.

This document reviews all the steps necessary to migrate away from JSS.

While you can use either of the following two options, the first is considered preferable:

1. Use styled or sx API

Codemod

We provide a codemod to help migrate JSS styles to styled API, but this approach increases the CSS specificity.

npx @mui/codemod v5.0.0/jss-to-styled <path>

Example transformation:

 import Typography from '@mui/material/Typography';
-import makeStyles from '@mui/styles/makeStyles';
+import { styled } from '@mui/material/styles';

-const useStyles = makeStyles((theme) => ({
-  root: {
-    display: 'flex',
-    alignItems: 'center',
-    backgroundColor: theme.palette.primary.main
-  },
-  cta: {
-    borderRadius: theme.shape.radius
-  },
-  content: {
-    color: theme.palette.common.white,
-    fontSize: 16,
-    lineHeight: 1.7
-  },
-}))
+const PREFIX = 'MyCard';
+const classes = {
+  root: `${PREFIX}-root`,
+  cta: `${PREFIX}-cta`,
+  content: `${PREFIX}-content`,
+}
+const Root = styled('div')(({ theme }) => ({
+  [`&.${classes.root}`]: {
+    display: 'flex',
+    alignItems: 'center',
+    backgroundColor: theme.palette.primary.main
+  },
+  [`& .${classes.cta}`]: {
+    borderRadius: theme.shape.radius
+  },
+  [`& .${classes.content}`]: {
+    color: theme.palette.common.white,
+    fontSize: 16,
+    lineHeight: 1.7
+  },
+}))

 export const MyCard = () => {
-  const classes = useStyles();
   return (
-    <div className={classes.root}>
+    <Root className={classes.root}>
       {/* The benefit of this approach is that the code inside Root stays the same. */}
       <Typography className={classes.content}>...</Typography>
       <Button className={classes.cta}>Go</Button>
-    </div>
+    </Root>
   )
 }

Manual

We recommend sx API over styled for creating responsive styles or overriding minor CSS. Read more about sx here.

 import Chip from '@mui/material/Chip';
-import makeStyles from '@mui/styles/makeStyles';
+import Box from '@mui/material/Box';
+import { styled } from '@mui/material/styles';

-const useStyles = makeStyles((theme) => ({
-  wrapper: {
-    display: 'flex',
-  },
-  chip: {
-    padding: theme.spacing(1, 1.5),
-    boxShadow: theme.shadows[1],
-  }
-}));

 function App() {
-  const classes = useStyles();
   return (
-    <div>
-      <Chip className={classes.chip} label="Chip" />
-    </div>
+    <Box sx={{ display: 'flex' }}>
+      <Chip label="Chip" sx={{ py: 1, px: 1.5, boxShadow: 1 }} />
+    </Box>
   );
 }

In some cases, you might want to create multiple styled components in a file instead of increasing CSS specificity.

For example:

-import makeStyles from '@mui/styles/makeStyles';
+import { styled } from '@mui/material/styles';

-const useStyles = makeStyles((theme) => ({
-  root: {
-    display: 'flex',
-    alignItems: 'center',
-    borderRadius: 20,
-    background: theme.palette.grey[50],
-  },
-  label: {
-    color: theme.palette.primary.main,
-  }
-}))
+const Root = styled('div')(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  borderRadius: 20,
+  background: theme.palette.grey[50],
+}))

+const Label = styled('span')(({ theme }) => ({
+  color: theme.palette.primary.main,
+}))

 function Status({ label }) {
-  const classes = useStyles();
   return (
-    <div className={classes.root}>
-      {icon}
-      <span className={classes.label}>{label}</span>
-    </div>
+    <Root>
+      {icon}
+      <Label>{label}</Label>
+    </Root>
   )
 }

2. Use tss-react

The API is similar to JSS makeStyles, but under the hood, it uses @emotion/react. It also features much better TypeScript support than v4's makeStyles.

In order to use it, you'll need to add it to your project's dependencies:

With npm:

npm install tss-react

With yarn:

yarn add tss-react

You will also need to edit your providers:

 import { render } from 'react-dom';
-import { StylesProvider } from '@material-ui/core/styles';
+import createCache from '@emotion/cache';
+import { CacheProvider } from "@emotion/react";

+export const muiCache = createCache({
+  'key': 'mui',
+  'prepend': true,
+});

 render(
-  <StylesProvider injectFirst>
+  <CacheProvider value={muiCache}>
     <Root />
-  </StylesProvider>,
+  </CacheProvider>,
   document.getElementById('root')
 );

Codemod

We provide a codemod to help migrate JSS styles to the tss-react API.

npx @mui/codemod v5.0.0/jss-to-tss-react <path>

Example transformation:

 import React from 'react';
-import makeStyles from '@material-ui/styles/makeStyles';
+import { makeStyles } from 'tss-react/mui';
 import Button from '@mui/material/Button';
 import Link from '@mui/material/Link';

-const useStyles = makeStyles((theme) => {
+const useStyles = makeStyles()((theme) => {
   return {
     root: {
       color: theme.palette.primary.main,
     },
     apply: {
       marginRight: theme.spacing(2),
     },
   };
 });

 function Apply() {
-  const classes = useStyles();
+  const { classes } = useStyles();

   return (
     <div className={classes.root}>
       <Button component={Link} to="https://support.mui.com" className={classes.apply}>
         Apply now
       </Button>
     </div>
   );
 }

 export default Apply;

If you were using the $ syntax and clsx to combine multiple CSS classes, the transformation would look like this:

 import * as React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
-import clsx from 'clsx';
+import { makeStyles } from 'tss-react/mui';

-const useStyles = makeStyles((theme) => ({
+const useStyles = makeStyles<void, 'child' | 'small'>()((theme, _params, classes) => ({
   parent: {
     padding: 30,
-    '&:hover $child': {
+    [`&:hover .${classes.child}`]: {
       backgroundColor: 'red',
     },
   },
   small: {},
   child: {
     backgroundColor: 'blue',
     height: 50,
-    '&$small': {
+    [`&.${classes.small}`]: {
       backgroundColor: 'lightblue',
       height: 30
     }
   },
 }));

 function App() {
-  const classes = useStyles();
+  const { classes, cx } = useStyles();
   return (
     <div className={classes.parent}>
       <div className={classes.child}>
         Background turns red when the mouse hovers over the parent.
       </div>
-      <div className={clsx(classes.child, classes.small)}>
+      <div className={cx(classes.child, classes.small)}>
         Background turns red when the mouse hovers over the parent.
         I am smaller than the other child.
       </div>
     </div>
   );
 }

 export default App;

The following is a comprehensive example using the $ syntax, useStyles() parameters, merging in classes from a classes prop (see doc) and an explicit name for the stylesheet.

-import clsx from 'clsx';
-import { makeStyles, createStyles } from '@material-ui/core/styles';
+import { makeStyles } from 'tss-react/mui';

-const useStyles = makeStyles((theme) => createStyles<
-  'root' | 'small' | 'child', {color: 'primary' | 'secondary', padding: number}
->
-({
-  root: ({color, padding}) => ({
+const useStyles = makeStyles<{color: 'primary' | 'secondary', padding: number}, 'child' | 'small'>({name: 'App'})((theme, { color, padding }, classes) => ({
+  root: {
     padding: padding,
-    '&:hover $child': {
+    [`&:hover .${classes.child}`]: {
       backgroundColor: theme.palette[color].main,
     }
-  }),
+  },
   small: {},
   child: {
     border: '1px solid black',
     height: 50,
-    '&$small': {
+    [`&.${classes.small}`]: {
       height: 30
     }
   }
-}), {name: 'App'});
+}));

 function App({classes: classesProp}: {classes?: any}) {
-  const classes = useStyles({color: 'primary', padding: 30, classes: classesProp});
+  const { classes, cx } = useStyles({
+    color: 'primary',
+    padding: 30
+  }, {
+    props: {
+      classes: classesProp
+    }
+  });

   return (
     <div className={classes.root}>
       <div className={classes.child}>
         The Background take the primary theme color when the mouse hovers the parent.
       </div>
-      <div className={clsx(classes.child, classes.small)}>
+      <div className={cx(classes.child, classes.small)}>
         The Background take the primary theme color when the mouse hovers the parent.
         I am smaller than the other child.
       </div>
    </div>
  );
}

export default App;

After running the codemod, search your code for "TODO jss-to-tss-react codemod" to find cases that the codemod could not handle reliably.

There may be other cases beyond those with TODO comments that are not handled fully by the codemod—particularly if parts of the styles are returned by functions.

If the styles buried within a function use the $ syntax or useStyles params, then those styles won't be migrated appropriately.

To ensure that your class names always includes the actual name of your components, you can provide the name as an implicitly named key (name: { App }).

See this tss-react doc for details.

You may end up with eslint warnings like this one if you deconstruct more than one item.

Don't hesitate to disable eslint(prefer-const), like this in a regular project, or like this in a CRA.

withStyles()

tss-react also features a type-safe implementation of v4's withStyles().

-import Button from '@material-ui/core/Button';
+import Button from '@mui/material/Button';
-import withStyles from '@material-ui/styles/withStyles';
+import { withStyles } from 'tss-react/mui';

 const MyCustomButton = withStyles(
+  Button,
   (theme) => ({
     root: {
       minHeight: '30px',
     },
     textPrimary: {
       color: theme.palette.text.primary,
     },
     '@media (min-width: 960px)': {
       textPrimary: {
         fontWeight: 'bold',
       },
     },
   }),
-)(Button);
+);

 export default MyCustomButton;

Theme style overrides

Global theme overrides are supported out of the box by TSS.

Follow the instructions in the relevant section of the Breaking changes doc, and provide a name to makeStyles.

In Material UI v5, style overrides also accept callbacks.

By default, TSS is only able to provide the theme. If you want to provide the props and the ownerState, please refer to this documentation.

Complete the migration

Once you migrate all of the styling, remove unnecessary @mui/styles by uninstalling the package.

With npm:

npm uninstall @mui/styles

With yarn:

yarn remove @mui/styles