material-ui: [v5] CSS injection order is wrong

I’m using the following pattern in my code, but it doesn’t work anymore in v5 (tested with 5.0.0-alpha.20). My custom styles get overwritten by other styles (likely coming from the new styling mechanism):

const styles = theme => ({
    title: {
        [theme.breakpoints.down('sm')]: theme.typography.body1
    }
})

function App({ classes }) {
    return <Typography variant="h5" classes={{h5:classes.title}}>Hello</Typography>;
}

const StyledApp = withStyles(styles)(App);

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 24
  • Comments: 59 (35 by maintainers)

Most upvoted comments

I was aware of injectFirst attribute, but it didn’t work in my app. After some testing with the codesandbox, it appears the problem come from importing StylesProvider from @material-ui/core/styles (v4 code, which I’m currently migrating) instead of @material-ui/core. You can verify that in your codesandbox.

@goffioul now that the Typography, Button, etc components are migrated to emotion, you need to use the StylesProvider exported from ‘@material-ui/core’ with the injectFirst option, in order for the CSS injection order to be correct between emotion and JSS. It is explained here: https://next.material-ui.com/guides/interoperability/#css-injection-order.

This is only required for the time of the migration (v5-alpha phase). Once we do no longer depend on JSS, the order should be correct.

Here is a codesandbox with a working example - https://codesandbox.io/s/devtools-material-demo-forked-ethyg?file=/index.js You will notice that the <Demo /> is wrapped with the StylesProvider component.

import * as React from "react";
import ReactDOM from "react-dom";
import StylesEngineProvider from "@material-ui/core/StylesEngineProvider";
import Demo from "./demo";

ReactDOM.render(
  <StyledEngineProvider injectFirst>
    <Demo />
  </StyledEngineProvider>,
  document.querySelector("#root")
);

Hi guys, I am trying to figure this out, using Styled Components, any tips? It works ok during SSR, but during the client rehydratation, I have JSS style tags that resets my button style, removing their background.

My current setup is basically like this:

    <StyledEngineProvider injectFirst>
      <ThemeProvider theme={memoizedTheme}>{children}</ThemeProvider>
    </StyledEngineProvider>

I use both makeStyles and Styled Components during this v4 to v5 transition phase.

I fail to understand what is expected. It seems to me that StyledEngine will manage my v5 styles, and StylesProvider my v4 legacy styles, so I get it I might need a combination of both to get the right order. But I cannot find the right setup.

This one seems to put Styled Components upper in the tree, but that’s JSS I want to move up.

Also tried this one:

    <StyledEngineProvider injectFirst>
      <StylesProvider injectFirst>
        <ThemeProvider theme={memoizedTheme}>{children}</ThemeProvider>
      </StylesProvider>
    </StyledEngineProvider>
// or similarly
    <StyledEngineProvider>
      <StylesProvider injectFirst>

image

Part of the styles are indeed upper, but not all of them! One of them in particular is resetting my button with a transparent background, as if it was some kind of default style coming from the theme. I don’t get how to force to get upper in the tree, injectFirst doesn’t have any effect.

Just to make it clearer, here are my imports:

import {
  ThemeProvider,
  StyledEngineProvider,
  adaptV4Theme,
} from "@mui/material/styles";
import { StylesProvider } from "@mui/styles"

Sorry for the lack of reproduction, this is a real-life setup. Basically half the JSS styles don’t resepect injectFirst.

Update: I am running a few more experiments today. Using a custom insertionPoint in my JSS config will get my styles back together, but still not at the right place (injected after Styled Components, and not before).

It seems that what I want is just StylesEngine injectFirst, to move JSS up in the head. I don’t need StyledEngineProvider because Styled Components is ok.

This issue seems to boil down to jss not respecting the insertion point for core Material UI elements. injectFirst is behaving as expected, but only half the JSS styles are respecting the generated insertion point. I’ve tried to add manuallty the <!--mui-inject-first--> comment so it’s there even during SSR, in case it was an issue with the insertion point not being present at the right time, but no luck. This seems to affect the styles that are not server-rendered, maybe an instance of this issue? https://github.com/cssinjs/jss/issues/798

My palliative in the meantime, that will move data-jss upper at first render:

/**
 * A quickfix to move JSS styles upper in the tree
 */
export const JssMover = ({ children }) => {
  useEffect(() => {
    console.warn('JssMover component moves data-jss styles to the top');
    console.warn('See https://github.com/mui-org/material-ui/issues/24109');
    const elements = document.querySelectorAll('[data-jss][data-meta^=Mui],[data-jss][data-meta=ForwardRef]');
    document.head.prepend(...elements);
  }, []);
  return children;
};

New update: my JSS mover fixes some stylings… but breaks some others, as core Styled Components based styles are now overriding my own components. Basically, the JSS are kinda reversed: my custom styles are a the top, but the raw types are still at the bottom. I’ve updated it so it moves only the “Mui*” styles that seems to be the faulty one (the other have “makeStyles” as Meta and work correctly)

As a developer using material-ui (not developing it), initially I totally missed the different import. Using the same name obviously adds to the confusion, because old code is already importing StylesProvider and you don’t realize you have to import it from another module to get a different implementation. It’s also not obvious that when migrating code that uses JSS, you actually need to use both providers to manage the import order properly (or is there a better alternative?). Yes, the doc says you need to use injectFirst in your StylesProvider, but it doesn’t tell you explicitly which one (in my case, I used injectFirst on the StylesProvider I already imported in my code, but it was the old one, and that didn’t have the wanted effect).

If the target is to have a drop-in replacement for the v4 styling engine, I’d say using the same name is a good option. The problem I’m having is just a transient situation in alpha-grade code, I can live with that. Maybe a warning when using the old StylesProvider would help detect such scenario. If a drop-in replacement is not on the table, then I’d go with a different name.

Wouldn’t be possible without a tip from @mnajdova - if you see styles coming from a “static” class name (like MuiButtonBase-root) and if those styles are not under your control, but generated by JSS, then it’s most likely that it’s some rogue v4 in your codebase.

The solution in https://github.com/mui-org/material-ui/issues/24109#issuecomment-750794821 worked for me. For others who might be lacking the <StylesProvider/>, I’ll just note that one very salient phenomenon is that all components that were previously round (like Fab, DatePicker days, etc.) are rectangular (because the border-radius: 0 takes precedence). Just writing this down so that others can find this issue faster through GitHub search.

@sep2 emotion doesn’t provide such capability, only styled-components does. The closest is https://emotion.sh/docs/@emotion/cache#prepend. I’m going to open an issue on emotion as I can’t find any duplicates.

Ah ok, I see the confusion now. The examples use custom emotion cache, which means that it is enough toa add prepend: true as an option and it would do the same as the StyledEngineProvider with injectFirst would do. We have that as an explanation on the docs:

image

Reference: https://mui.com/guides/interoperability/#main-content

If you want to use both emotion and JSS, you should configure and add the styles in the correct order for SSR, this is how we do it for our documentation: https://github.com/mui/material-ui/blob/master/docs/pages/_document.js

Why is use of <StylesProvider injectFirst> not implemented in the NextJS MUI example docs?

Starting a new app and wanted do things the “right” way instead of using deprecated JSS. However while using v5 with emotion we had to come here to learn about <StylesProvider> to avoid littering our codebase with !important

Once we added in this secret super power Component in _app.js our styles were finally applying correctly.

UPDATE: Didn’t realize it had changed from @material-ui to @mui. Double checked my deps in package.json and saw entries for @material-ui and @mui. Reinstalled with just @mui and updated import paths properly fixed this issue.

if you’re using @mui/x-data-grid make sure to use the peer dependency of @material-ui/core version ^5.0.0-beta.0. For me, it was the last remaining legacy 4.x module that was inject jss.

Found the issue was on my end. Apparently, the codemod missed some of the imports (and there was a “rouge” peerDep for v4 (courtesy of mdi-material-ui), allowing the import to almost work). Updating these to v5 imports resolved the issue I was having. Thanks so much @Andarist for pointing this out!

Can I still use insertionPoint like in v4? I would like MUI to override only some of the css only.

Actually, there is already an issue with it: https://github.com/emotion-js/emotion/issues/2037.


@mnajdova We probably want to progressively remove injectFirst from the documentation as we have migrated all the components to em/sc and are close for the docs as well.

None of the solution above worked for me. theme override(styleOverrides) always take precedence of styling comes from makeStyles API

import {  StyledEngineProvider } from '@material-ui/core';
import { ThemeProvider } from '@material-ui/core/styles';
import { StylesProvider } from '@material-ui/styles';

return (
    <StyledEngineProvider injectFirst>
      <StylesProvider>
        <ThemeProvider theme={theme}>
        ...
        </ThemeProvider>
      </StylesProvider>
    </StyledEngineProvider>
  );

Edit: Nevermind, the injection order was correct(emotion styles comes first and then JSS), for me it was a specificity issue, somehow the styling logic of components from v5 has changed to have higher CSS specificity, so I had to increase CSS specificity of existing style key like below.

arrow: {
  '&&': {
    transform: 'translate(0px, 0px)',
    width: '2em',
    height: '2em',
  },
}

Somewhat rough solution for a very similar problem (from what I understand about makeStyles): https://github.com/emotion-js/emotion/issues/1853#issuecomment-623349622

This is intentionally not super easy to do as we encourage 0-config SSR to be used 😉

I’m not super sure how things will play out but the current 0-config SSR strategy (or something close to it) might be the base for handling server components. Some context here: https://github.com/reactjs/rfcs/pull/189#discussion_r548468803 . It’s, of course, not certain - I have not received any feedback there and some other solution might be figured out along the way.

@oliviertassinari any plans to add more to the docs regarding the styled/experimentalStyled hooks?

It’s actually a bit more complicated than that, and I actually need to use the 2 styles providers, like the following:

import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import StyledEngineProvider from '@material-ui/core/StyledEngineProvider';
import { create } from 'jss';
import rtl from 'jss-rtl';

const jss = create({ plugins: [...jssPreset().plugins, rtl()] });

render(
    <StyledEngineProvider injectFirst>
        <StylesProvider jss={jss}>
            <App />
        </StylesProvider>
    </StyledEngineProvider>,
    document.getElementById('app')
);

Thank you very much for the explanation, this helped a lot. I think I got confused with all the possibilities and namings. It’s much more clear now 😃

<StyledEngineProvider /> doesn’t work well with SSR if you are having issues with SSR and the emotion cache giving nothing (which may not be obvious) it’s because of the StyledEngineProvider.

On the other hand without StyledEngineProvider then non-SSR mechanism don’t work well.

There was nothing wrong with JSS, but emotion and JSS “DO NOT” play nice with each other.

I’ve even had situations where the render changes unexpectedly as if hitting some race conditions.

For anyone considering migrating to v5 if you are happy with v4 it’s not worth it. It’s been 2 weeks for me, and I think I have to rewrite everything to drop JSS because emotion and JSS refuse to work together, I’ve tried every trick in the book, it’s not reliable.

There hasn’t been any activity on the issue for over a month and a fix was provided for the initial issue. I am closing it.

As an exception - I can help you if you send me a replay of the problem privately and describe the issue you are facing in detail (both what happens and what is your expected result)

I’d be happy to share a recording provided it can be privatly

I couldnt recommend https://replay.io/ enough for debugging tricky cases

On my side I just solved it by finishing the upgrade process to completely get rid of JSS. I strongly suspect it was related to multiple instance of mui / having old versions etc. So very difficult to repro. Anyway that’s a temporary issue part of the migration process so no huge deal.

I couldn’t fix my issue with injection order, hover the migration path using tss-react is brilliant, it worked rather quickly. One thing missing in the doc though, is that when migration to v4, we should also be careful about migrating dependencies. It’s easy to forget that your app is somehow using material-ui-search-bar for some reason => it will unexpectedly load v4 into your app and you’ll have jss styles despite having migrated everything. In this case, the dependency must also be migrated (opening a PR, getting rid of it, copying into your own code etc. will do the job). For instance, mdi-material-ui is in the proces of upgrading, but still using the old package name => you might want to wait a few week before triggering a migration if you strongly depend on it.

@mnajdova We probably want to progressively remove injectFirst from the documentation as we have migrated all the components to em/sc and are close for the docs as well.

Agree, I would wait until we migrate the demos to emotion as well, as there are still some JSS usages there 👍

we are not wanting to load 2 css-in-js solutions if we dont have to

@jcursoli In theory, it should be easy to implement the withStyles API using the ClassNames helper of emotion. If you want to give it a try in codesandbox and share the result, it would be awesome.

For makeStyles, I doubt we can have an adapter. But maybe @Andarist would have an idea.

I am facing similar issue with new fresh project & none of the suggested solution working for me. theme configuration & custom styles are getting overridden by material default styles.

I am using following versions:

    "@emotion/react": "^11.1.4", 
    "@emotion/styled": "^11.0.0",
    "@material-ui/core": "^5.0.0-alpha.24",

import { ThemeProvider, StylesProvider, jssPreset } from '@material-ui/core/styles';
import { create } from 'jss';
import CssBaseline from '@material-ui/core/CssBaseline';
import StyledEngineProvider from '@material-ui/core/StyledEngineProvider';

const jss = create({ plugins: [...jssPreset().plugins] });

// other code

 <StyledEngineProvider injectFirst>
      <StylesProvider jss={jss}>
        <LocaleContext.Provider value={{ lang, setLanguage }}>
          <IntlProvider
            messages={messages}
            locale={getShortLocale(lang)}
            defaultLocale={getShortLocale(Locale.lang)}
          >
            <ThemeProvider theme={theme}>
              <Provider store={store}>
                <CssBaseline />
                <Router />
              </Provider>
            </ThemeProvider>
          </IntlProvider>
        </LocaleContext.Provider>
      </StylesProvider>
   </StyledEngineProvider>

@oliviertassinari @mnajdova Please assist. Thanks!

Hello, I’ve tried to follow the original sandbox demo and make it work with emotion styles but without no luck.

Here is the demo with emotion. I’ve changed AppBar to be positioned at the bottom, but it stays at the top due to issues with css ordering.

If you import legacy StylesProvider from @material-ui/styles, then the custom emotion styles will start to work as expected, but all the mui components migrated to emotion (e.g. AppBar or Button) will have broken css order.

In this transition period, until we migrate all component to emotion, you cannot have both combinations (JSS components & emotion overrides & emotion components & JSS overrides, simply because in that case we don’t have predictable order of CSS injection we should expect). We recommend if you want to start early to adopt your overrides to use emotion to migrate it only on components which are already migrated to use emotion by default. You can track the progress here - https://github.com/mui-org/material-ui/issues/24405

Because emotion styles now have to be injected before JSS styles, emotion’s css prop can no longer be used to override styles of components that are still using JSS internally unless !important is used. It seems that the only solution is to migrate from emotion to JSS for those components, and only once MUI fully converts to emotion - migrate back. Is there a better way?

@mainfraame Prefer using modules coming from @material-ui/styled-engine (emotion/sc) and avoid modules from @material-ui/styles (JSS)