DEV Community

a-tonchev
a-tonchev

Posted on • Updated on

Material UI 5 - the easiest way to migrate from makeStyles to emotion

Material UI version 5 has cool new stuff, and also many breaking changes. The migration tool is also amazing, but the style migration might not be that easy. Migrating a huge project could be a real pain.

Fortunately, there is a way to make a new, easy way to simply migrate the existing structure to emotion. Lets start:

Styles Root

At first, don't forget to setup correctly the root styles, emotion's ThemeProvider should override the default Material designs:

import React from 'react';
import { ThemeProvider as MuiThemeProvider, StylesProvider } from '@material-ui/core/styles';
import { ThemeProvider } from '@emotion/react';

const theme = {
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
};

const App = () => (
  <StylesProvider injectFirst>
    <MuiThemeProvider theme={theme}>
      <ThemeProvider theme={theme}>
        // All my components
      </ThemeProvider>
    </MuiThemeProvider>
  </StylesProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

The custom hook

For the custom hook, we will make use of the framework agnostic @emotion/css library, that can generate and inject classes from objects. More here

Then, lets make a custom hook, that can rebuild our makeStyles:

import { useMemo } from 'react';
import { css } from '@emotion/css';
import { useTheme } from '@emotion/react';

const useClasses = stylesElement => {
  const theme = useTheme();
  return useMemo(() => {
    const rawClasses = typeof stylesElement === 'function'
      ? stylesElement(theme)
      : stylesElement;
    const prepared = {};

    Object.entries(rawClasses).forEach(([key, value = {}]) => {
      prepared[key] = css(value);
    });

    return prepared;
  }, [stylesElement, theme]);
};

export default useClasses;

Enter fullscreen mode Exit fullscreen mode

This component will receive a object or a function with the styles, and create corresponding classes.

Then the last Step is - rework:

The old components

// TODO: Unwrap the function from makeStyles and rename useStyles
const useStyles = makeStyles(theme => ({
  paper: {
    marginTop: theme.spacing(8),
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  ...
}));


const TheComponent = () => {
 const classes = useStyles(); // useStyles from Material UI
}

Enter fullscreen mode Exit fullscreen mode

will become

// TODO: Unwrap the object from makeStyles and rename useStyles
const styles = theme => ({
  paper: {
    marginTop: theme.spacing(8),
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  ...
});

const TheComponent = () => {
 const classes = useClasses(styles); // useStyles from custom hook
}

Enter fullscreen mode Exit fullscreen mode

You can try also with pure object of classes:

The old component

// TODO: Unwrap the object from makeStyles and rename useStyles
const useStyles = makeStyles({
  paper: {
    marginTop: theme.spacing(8),
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  ...
});

const TheComponent = () => {
 const classes = useStyles(); // useStyles from Material UI
}
Enter fullscreen mode Exit fullscreen mode

will become:

const styles = {
  paper: {
    marginTop: theme.spacing(8),
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  //...
};

const TheComponent = () => {
 const classes = useClasses(styles); // useStyles from custom hook
}
Enter fullscreen mode Exit fullscreen mode

In summary:

  1. We setup the ThemeProvider and StylesProvider in our root component
  2. We create a custom hook useStyles
  3. We get rid of the makeStyles and unwrap the styles from it
  4. Rename useStyles e.g. to styles, because it is no more a hook
  5. Replace useStyles call inside of the component with our useClasses hook, while we put the styles object/function as argument

And with just a little bit rework, we already use emotion :)

With this approach, we managed to migrate a 2-years Project in 1 hour.

If this simple function is not enough for you, like you need to use nested objects in the classes, typescript, withStyles or more, you can take a look at tss-react

Best Regards
Anton Tonchev
JUST-SELL.online

Top comments (18)

Collapse
 
garronej profile image
Garrone Joseph • Edited

I am biased on the subject as I am the author or tss-react (the second way recommended by mui to migrate from v4 to v5) but I wouldn't advise to actually implement the method described here because:

  • If you are using typescript: It provides no types.
  • No SSR (Next.js) support ( styles will be loaded client side)
  • No nested selectors (the $ syntax of JSS)
  • No withStyles()
  • Hard to debug, all classes will be mui-xxxxx.
  • This useStyles() accept no prams.
  • It relies on @emotion/css which is different from @emotion/react. Mui depends on @emotion/react
  • No mention of how to prioritize one class over another.

I really don't want to dunk on an otherwise very good article. I just think the case against actually implementing it had to be made.

Collapse
 
atonchev profile image
a-tonchev

I also would not recommend this approach when you want to use Nested Selectors or withStyles.

  • I don't made it capable of typescript, just because I don't like Typescript. But surely it is not that big deal to adjust it :)
  • Regarding SSR, you are not right, emotion/css is capable of SSR. You just need one or two more tweaks, if you need it:

emotion.sh/docs/ssr#api

  • It is also not hard to debug. If you really need to debug, you can do following:
  Object.entries(rawClasses).forEach(([key, value = {}]) => {
      prepared[key] = css(value, IS_DEV ? {
        label: key,
      } : undefined);
    });
Enter fullscreen mode Exit fullscreen mode

This way you can
a) debug ALL the class name in development and
b) you reuse classes in production. If you use the label / class names in production then you can not reuse them

If you need additional parameters, just add them to useClasses :) it is a simple function.

  • Yes it relies on @emotion/css, which relies on stylis, @emotion/serialize, @emotion/cache, @emotion/sheet, etc. ... basically exact the same base libraries on which relies @emotion/react :)

In general, this is a self made small function that can help someone (like us) to migrate easily from mui4 to mui5, without the need to install any third party library.

In our use-case the mui5 migration scripts did the most, the only problem was the makeStyles/useStyles hook, which could be easily solved with this simple 15 lines of code.

This function can easily be extended to use nested classes or typescript, or with any other functionality. Anyway if someone need more (complex) features or typescript I would also recommend to use tss-react :)

I even put right now this recommendation in my article! ;)

Collapse
 
garronej profile image
Garrone Joseph • Edited

I even put right now this recommendation in my article! ;)

It's very nice of you! Much appreciated! :D

But surely it is not that big deal to adjust it :)

This is, infact, extremely hard to adapt this API to work with type inference. I'd say the types definitions represent 90% of the overall effort that went into developing tss-react.

Regarding SSR, you are not right, emotion/css is capable of SSR. You just need one or two more tweaks.

It's not just a few tweaks. It is very hard to get working properly. However, user that chose to implement your approach can always refer to TSS to setup SSR.

It is also not hard to debug. If you really need to debug, you can do following

Fair enough, it's also the approach implemented in TSS (except that we can optionally add the component name but you could easily add it as well).

If you need additional parameters, just add them to useClasses :) it is a simple function.

Fair enough

Yes it relies on @emotion/css, which relies on stylis, @emotion/serialize, @emotion/cache, @emotion/sheet, etc.

You are right, it isn't a very strong argument.

In our use-case the mui5 migration scripts did the most, the only problem was the makeStyles/useStyles hook, which could be easily solved with this simple 15 lines of code.

There is now a codemod in MUI for migrating from JSS to TSS. See doc

This function can easily be extended to use nested classes or typescript.

Again, with all due respect, no, I don't want people to think. "Well, if I ever need the nested selectors I'll just need to adapt my makeStyles a little bit". Getting selectors to work is a degree of magnitude harder than implementing the base hook.

Again. No hate whatsoever. Just helping people to make an informed decision.

Collapse
 
vicodinvic1 profile image
Vsevolod Pechenin

The most frustrating thing here is No nested selectors indeed

Collapse
 
suvo profile image
suvo

Hi @atonchev ,
I really liked your post and used it in my project, I have added a TS support and a withStyles replacement hook similar to the one you mentioned here
You can take a look at my post dev.to/subhadippal/upgrade-materia...

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

That's simply amazing. Thanks so much for the tip! It will save me the little hair I have left on my head now we are moving to v5.

Just a reminder for those coming here after the official launch, all the packages were renamed to @mui/

Collapse
 
huydhoang profile image
Huy • Edited

Good one. To people setting up brand new MUI v5 projects in 2021, the official way to override default styles now is through the sx prop or the styled() utility function. Custom theme properties could be accessed via importing your theme and wrapping components inside ThemeProvider.

import { ThemeProvider } from '@mui/system';
import theme from '../theme';

<ThemeProvider theme={theme}>
  <Container sx={{ bgcolor: theme.palette.primary.main, }}>
  ...
  </Container>
</ThemeProvider>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
atonchev profile image
a-tonchev • Edited

Acutally you can just use 'primary.main' for color:

  <Container sx={{ bgcolor: 'primary.main', }}>
     ...
  </Container>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
semigradsky profile image
Dmitry Semigradsky

I found github.com/garronej/tss-react It looks nice if you want to remain makeStyles/withStyles.

Collapse
 
syptic profile image
Samay Gandhi

Hey, I am using this method to roll over my makeStyles so they can work with both mui v4 and v5 components. Currently the issue I am facing is that base styles coming from the design library are overwriting the useClasses styles in the consuming app. To fix this, how can I increase the specificity of useClasses styles so they can override the base styles.

Collapse
 
atonchev profile image
a-tonchev

You need to setup by yourself the injection order:

mui.com/material-ui/migration/v5-s...

Collapse
 
zionx4ever profile image
Reinaldo Zambrano

I have several cuestions about this, i implemented it on a tab component, and worked very well, then tried to use it on a ListItem and did nothing, neither on a Accordion.

Collapse
 
atonchev profile image
a-tonchev

I am not sure that I understand the question. For me it works everywhere just fine. Maybe you try to style some of the sub-components of the ListItem and Accordion?

Collapse
 
edardev profile image
Edward Almanzar

What is the benefits of migrating to V5 from V4?

Collapse
 
semigradsky profile image
Dmitry Semigradsky

V4 will not receive any updates.

Collapse
 
edardev profile image
Edward Almanzar

I had a nightmare migrating it with a gatsby.js app, I had to rollback to v4 😭

Collapse
 
bayaroch profile image
bayaroch

Can you make gist on github. Please make TypeScript version of useClasses

Collapse
 
atonchev profile image
a-tonchev

I don't like TypeScript, sorry.