Dismiss announcement
Matheus Riolfi
Tint raises a $25M Series A round led by QED Investors to enable tech platforms to become insurers
Read more
Cross
Logo Icon

Get Started

Our experience migrating to Material UI v5

Our experience migrating to Material UI v5

What are the main updates brought by MUI v5? And what are the challenges we faced during the migration?

At Tint, we were using Material UI v4 and decided to migrate to MUI v5 recently. We thought we'd use this migration as an opportunity to share the challenges we have faced.

In this post, we are going to share how we performed a gradual migration and detail all the pitfalls we faced during the process.

🤷 Why upgrade?

We decided to upgrade to V5 to get the most out of MUI's new features and changes:

  • 🚀 Better performance: bundle size reduction and unstructured imports can be used when importing multiple components.
  • 💅 Easier and faster customization thanks to styled-components, the sx prop and new components.
  • 💎 New components that have evolved from the lab: Pagination, Rating, Skeleton, Autocomplete, Toggle, advanced tables, MultiSelect, Dropzone buttons, and many more!
  • 📄 Better documentation, including a Typescript tab for each component.

Moreover, Material UI v4 reached its end-of-life and is no longer supported by the MUI organization.

image (6)-1

The key words to summarize: better efficiency, more performance, and easier customization! ✊

Finally, the new MUI version is also equipped with refreshed design kits (Figma, Sketch, Adobe XD), to the utmost pleasure of our designers 🤗

🧪 Main takeaways

We decided to avoid a big-bang style migration to not put extra toil on our growing engineering team. The components were migrated little by little, allowing the team to keep up with each change.

We're recommending following the official migration guide from MUI; we will only focus on a few items that took us a lot of time and that we wanted to highlight.

Update MUI core version

The easiest step, and the beginning of the hostilities!

npm install @mui/material @mui/styles
// or with yarn
yarn add @mui/material @mui/styles

To migrate component imports, MUI provides the following codemod which takes care of changing everything for us.

npx @mui/codemod v5.0.0/preset-safe <path>

For small projects, the migration can be very fast with this simple tool. However, in our case, we could not use it because our whole application was relying too much on the makeStyles helper (which is a common but legacy styling solution for MUI v4), which we wanted to remove.

The makeStyles hook is no longer part of the v5 API but is still available as a helper to ease the migration.

Importing both v4 and v5 in the same app

Because a migration can be very long depending on the size of your project, it is critical to proceed with caution, moving one step at a time.

In our case, we chose to opt for this gradual strategy and import both the v4 and the v5 themes to avoid migrating everything at once:

import v4Theme from "@ui/src/v4/theme";
import { ThemeProvider as MuiThemeProvider } from "@material-ui/core/styles";
{
  /* V4 imports */
}
import { ThemeProvider } from "@mui/material/styles";
import theme from "@ui/src/theme";
{
  /* Precious v5 imports */
}

const App = () => {
  return (
    <MuiThemeProvider theme={v4Theme}>
      <ThemeProvider theme={theme}>
        {/* The rest of your application */}
        {/* Both v4 and v5 components will work here */}
      </ThemeProvider>
    </MuiThemeProvider>
  );
};

export default App;

The MUI Theme Provider uses a different React context to dissociate the components from Material UI v4 and MUI v5. Thus, we were able to start our migration page-by-page, with each component being able to recognize its own theme. 🙏

🕶️ Styling

Having upgraded the MUI core, we can now focus on upgrading our components one file at a time.

Bye JSS, welcome Emotion!

Material-UI v4 used JSS as its CSS library. In the new version, they transitioned to Emotion for several reasons:

  • The maintenance and development of their style engine solution were too time-consuming.
  • JSS had poor performance.
  • The syntax required to overwrite styling was too complex.
  • The majority of the React community has not chosen to support JSS.
  • A lot of developers already use styled-components to override Material-UI's styling.

You can find more details here: https://github.com/mui/material-ui/issues/22342.

Emotion

Emotion is a CSS-in-JS library ideal for dynamic styling.

The new MUI version comes with Emotion and styled-components. We chose Emotion, but you can read the comparison between both in this superb article from LogRocket.

  • @mui/styled-engine: implemented with Emotion (by default).
  • @mui/styled-engine-sc: implemented with styled-components.

In any case, MUI provides the sx prop and the  styled API as wrappers around Emotion to customize the component styles.

Injecting CSS with StyledEngineProvider

During the migration, if using both v4 and v5 theme providers, we need to use the StyledEngineProvider, in order for MUI to inject Emotion CSS first (before JSS):

import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override MUI's styles.🤗*/}
    </StyledEngineProvider>
  );}

Once we are no longer dependent on JSS, the order should be correct and this wrapper can be removed.

Replacing makeStyles

In v5, makeStyles is imported from @mui/styles, but this library depends on JSS as a styling solution. JSS is no longer included in the @mui/material library anymore but still is in @mui/styles, which isn't compatible with React.StrictMode or React 18.

Removing the makeStyles helper was the biggest effort of this migration! It's now considered deprecated in the MUI API, and they plan to remove it in the next version.

In the meantime, it is encouraged to adopt the styled API and the sx prop.

The MUI team offers us a solution to automate the migration:

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

Again, we recommend avoiding the usage of codemods for the simple reason that it will make the codebase harder to read.

Here is an example of what the codemod produced:

Before:

const useStyles = makeStyles(() => ({
    archiveButton: {
        color: red,
    },
}));

export const ButtonExample = () => {
    const classes = useStyles();

    return <Button className={classes.archiveButton}>;
};

After:

const PREFIX = 'ButtonExample';
const classes = {
    archiveButton: `${PREFIX}-archiveButton`,
};

const Root = styled('button')(() => ({
    [`& .${classes.archiveButton}`]: {
        color: red,
    },
}));

export const ButtonExample = () => {
    return (
        <Root>
            <Button className={classes.archiveButton}>
        </Root>
    );
};

Instead, we preferred to just rewrite the following code with the sx prop:

export const ButtonExample = () => (
  <Button sx={{ color: 'red' }}>
);

And with only this, it's DONE! Simple and efficient! 🎉

In parallel, we have set a ESLint rule concerning the imports to help us during the migration:

    'no-restricted-imports': [
        'error',
        {
            paths: [
                {
                    name: '@material-ui/core',
                    importNames: ['makeStyles'],
                    message:
                        "The use of makeStyles is deprecated by MUI, please prefer the use of the 'sx' or 'styled' props.",
                },
            ],
            patterns: [
                {
                    group: ['@material-ui/*'],
                    message:
                        "Material UI v4 is deprecated, please use MUI v5 '@mui/*'.",
                },
            ],
        },
    ],

SX prop and conditional styling

With Material UI v4, we relied a lot on makeStyles to apply conditional styling to our components. Instead, we're now writing ✨ just JavaScript™ ✨ as shown below.

Before:

const useStyles = makeStyles(() => ({
    boxChangeColor: {
        backgroundColor: ({ clicked }) =>
            clicked ? 'primary.dark' : 'secondary.light'
        ),
    },
}));

export default function ToogleBox() {
    const [clicked, setClicked] = useState(false);

    const handleClick = () => {
        setClicked(click => !click)
    };

    return (
        <>
            <Button onClick={handleClick}>Toogle</Button>
            <Box className={boxChangeColor} />
        </>
    );
}

After:

export default function ToogleBox() {
  const [clicked, setClicked] = useState(false);

  const handleClick = () => {
    setClicked((click) => !click);
  };

  return (
    <>
      <Button onClick={handleClick}>Change</Button>
      <Box
        sx={{
          bgcolor: clicked ? "primary.dark" : "secondary.light",
        }}
      />
    </>
  );
}

The use of the sx prop is being able to manage a conditional rendering in a simple way.

Using styles shortcuts

MUI also has new custom property shortcuts.

<Slider sx={{ mb: 1 }} />
// This example is equivalent to
<Slider sx={{ marginBottom: theme.spacing(1) }} />

We chose to not make extensive use of those shortcuts as we find that it makes the code less readable for new engineers. 😕

But, if you like them, here are a few examples:

  • mt => marginTop
  • mx => marginX
  • pb => paddingBottom
  • etc

Bonus: The new stack component

We also took advantage of the migration to use the new Stack component. This one is a blessing.

In our codebase, we were using the Grid component a lot as a helper to use flexbox, and it made the code hard to read. In contrast, Stack, as a directional stack, makes the code more readable and accessible. 🕵️‍♀️

Before:

<Grid container direction="column" justifyContent="center" alignItems="center">
  <Grid item>
    <Box>Hello</Box>
  </Grid>
  <Grid item>
    <Box>From Tint!</Box>
  </Grid>
</Grid>

After:

<Stack justifyContent="center" alignItems="center">
  <Box>Hello</Box>
  <Box>From Tint!</Box>
</Stack>

In addition, there are also some exciting new components to use: Skeleton, Autocomplete, pagination and the loadingButton.

✌️Challenges we faced

Codemods that are doing more harm than good

As previously stated, we do not recommend the codemod to remove the makeStyles hook because it generates code that's much too complex.

Another one that we don't recommend is the following:

npx @mui/codemod v5.0.0/preset-safe <path>

This one is supposed to replace your MUI imports from @material-ui/ to @mui.

However, sometimes you end up with this kind of import:

// Before
import Grid from "@material-ui/core/Grid";
// After - Codemod output
import Grid from "@mui/material/Grid/Grid";
// Expected
import Grid from "@mui/material/Grid";

The double Grid is not a problem, but our unit tests - which are transpiled with Babel - output the following: 😥

Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

Totally revamped DatePicker

MUI lab DatePicker has been totally changed. Not only its API, but its whole look, feel, and UX.

It has been completely rewritten from scratch, the most important change being that the onChange callback is called multiple times while picking a date and time, while the onError callback API also changed.

Because of that, a lot of end-to-end tests broke. At this point, the documentation for the detailed migrations are not clear.

Moreover, since April 2022, the date picker has been moved again to MUI X.

Please read more details here: https://mui.com/material-ui/guides/pickers-migration/

Storybook and MUI v5

Because MUI and Storybook are both using Emotion, the MUI's theme conflicts with the storybook's theme.

By default, there will be no error shown, but the MUI theme will not be applied.

To solve this, it was necessary to overwrite Emotion's import path from the Storybook configuration. To do so, we used an alias :

const path = require('path');
const toPath = (filePath) => path.join(process.cwd(), filePath);

 module.exports = {
   stories: [
 // Your stories.
             ]
 //Change path Emotion.
      config.resolve = {
            ...config.resolve,
            alias: {
                ...config.resolve.alias,
                '@emotion/core': toPath('../../node_modules/@emotion/react'),
                'emotion-theming': toPath('../../node_modules/@emotion/react'),
            },
        };

But, if you have version 6.4 or above of Storybook, you can detach the dependency with the emotionAlias  boolean :

module.exports = {
  features: { emotionAlias: false }
}

👏 Final notes

On a large application, it's easier to import both MUI versions with each theme to migrate between versions gradually.

The sx prop is very handy and available on each component, saving us much precious time in the removal of makeStyles. However, when using many instances of this prop, it can be more efficient to use styled() .

MUI was great when we built the first steps of our product, a few years ago. But now that we have more resources, we are taking another path for our user experience. That might be a good topic for a future blog post: introducing the Tint Design System.

Zoé Carvallo

Zoé Carvallo

Software Engineer in charge of building the Tint design system, Zoé likes to play sports, even if she gets hurt regularly. Fortunately, her passion for design and development saves her health!

Create your own insurance and guarantee products today