Material-ui: [styles] Strict Mode support

Created on 24 Oct 2019  路  42Comments  路  Source: mui-org/material-ui

First of all, thank you all very much for the amazing material-ui.

I started my project from https://github.com/mui-org/material-ui/tree/master/examples/nextjs

Lately, I started to notice that my page after hydration looked weird - some styles were missing and some other times they were not applied to the expected elements.

Using React.StrictMode it tells me the following error:
Warning: Prop className did not match. Server: "MuiSvgIcon-root makeStyles-lightBulb-47" Client: "MuiSvgIcon-root makeStyles-lightBulb-48"

Current Behavior 馃槸

Looks like the classNames generated at server rendering are different to the ones generated at browser rendering time.

Expected Behavior 馃

Server-side rendering and client-side rendering should generate the same class names.

Steps to Reproduce 馃暪

Steps (Code Sandbox):

  1. https://codesandbox.io/s/nextjs-hge3h:
  2. Open developer tools (F12) and refresh code sandbox:
  3. You'll see:
    Warning: Prop className did not match. Server: "MuiSvgIcon-root makeStyles-lightBulb-47" Client: "MuiSvgIcon-root makeStyles-lightBulb-48"

Steps (Running locally)

  1. git clone https://github.com/bmvantunes/material-ui.git
  2. cd material-ui/examples/nextjs
  3. npm install
  4. npm run dev
  5. Open the browser, F12 and you'll see the error:
    Warning: Prop className did not match. Server: "MuiSvgIcon-root makeStyles-lightBulb-47" Client: "MuiSvgIcon-root makeStyles-lightBulb-48"

Context 馃敠

The reproduction of the error will look something like:
next-material

The only difference between my repository (example) and material-ui/examples/nextjs is the React.StrictMode instead of React.Fragment inside _app.js

Your Environment 馃寧

| Tech | Version |
| ----------- | ------- |
| Material-UI | latest (4.51) |
| React | latest (16.11.0) |
| Browser | Any browser |
| Node | 12 |
| Nextjs | latest (9.1.1)

PS - I saw that in the past some issues were created for something similar to this, but I couldn't apply the solutions on those issues to this specific issue. Most of those issues were using styled-components and none was using the material-ui example.

bug 馃悰 important styles

Most helpful comment

thank you for reporting it. I'm experiencing the same issue here..

All 42 comments

@oliviertassinari I think this is a bug, not a question.

If you see my reproduction steps, the only thing I did was to add StrictMode to material-ui/example/nextjs - no other code is changed

thank you for reporting it. I'm experiencing the same issue here..

I was able to solve it by going back to this versions:

"@material-ui/core": "3.9.3",
"@material-ui/styles": "3.0.0-alpha.10",
"next": "^9.1.1", // the latest version

_document.js:

/* eslint-disable prefer-destructuring */

import React from 'react'
import PropTypes from 'prop-types'
import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'

class MyDocument extends Document {
  render() {
    const { pageContext } = this.props

    return (
      <html lang="en" dir="ltr">
        <Head>
          <meta charSet="utf-8" />
          {/* Use minimum-scale=1 to enable GPU rasterization */}
          <meta
            name="viewport"
            content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
          />
          {/* PWA primary color */}
          <meta
            name="theme-color"
            content={
              pageContext ? pageContext.theme.palette.primary.main : null
            }
          />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

MyDocument.getInitialProps = ctx => {
  // Resolution order
  //
  // On the server:
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. document.getInitialProps
  // 4. app.render
  // 5. page.render
  // 6. document.render
  //
  // On the server with error:
  // 1. document.getInitialProps
  // 2. app.render
  // 3. page.render
  // 4. document.render
  //
  // On the client
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. app.render
  // 4. page.render

  // Render app and page and get the context of the page with collected side effects.
  let pageContext
  const page = ctx.renderPage(Component => {
    const WrappedComponent = props => {
      pageContext = props.pageContext
      return <Component {...props} />
    }

    WrappedComponent.propTypes = {
      pageContext: PropTypes.shape().isRequired,
    }

    return WrappedComponent
  })

  let css
  // It might be undefined, e.g. after an error.
  if (pageContext) {
    css = pageContext.sheetsRegistry.toString()
  }

  return {
    ...page,
    pageContext,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: (
      <React.Fragment>
        <style
          id="jss-server-side"
          // eslint-disable-next-line react/no-danger
          dangerouslySetInnerHTML={{ __html: css }}
        />
        {flush() || null}
      </React.Fragment>
    ),
  }
}

export default MyDocument

_app.js:

import React from 'react'
import App from 'next/app'

import getPageContext from '../lib/getPageContext'
import StylingProvider from '../lib/StylingProvider'

export default class MyApp extends App {
  constructor() {
    super()
    this.pageContext = getPageContext()
  }

  componentDidMount() {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side')

    console.log(jssStyles)
    if (jssStyles) {
      jssStyles.parentNode.removeChild(jssStyles)
    }
  }

  render() {
    const { Component, pageProps } = this.props

    return (
      <StylingProvider pageContext={this.pageContext}>
        <Component pageContext={this.pageContext} {...pageProps} />
      </StylingProvider>
    )
  }
}

StylingProvider.js:

import React from 'react'
import PropTypes from 'prop-types'
import { ThemeProvider, StylesProvider } from '@material-ui/styles'
import { MuiThemeProvider } from '@material-ui/core/styles'
import JssProvider from 'react-jss/lib/JssProvider'
import CssBaseline from '@material-ui/core/CssBaseline/CssBaseline'

const propTypes = {
  pageContext: PropTypes.shape().isRequired,
  children: PropTypes.node.isRequired,
}

function StylingProvider({ children, pageContext }) {
  return (
    <div>
      <JssProvider
        registry={pageContext.sheetsRegistry}
        generateClassName={pageContext.generateClassName}
      >
        <StylesProvider
          generateClassName={pageContext.generateClassName}
          sheetsRegistry={pageContext.sheetsRegistry}
          sheetsManager={pageContext.sheetsManager}
        >
          <MuiThemeProvider
            theme={pageContext.theme}
            sheetsManager={pageContext.sheetsManager}
          >
            <ThemeProvider theme={pageContext.theme}>
              <CssBaseline />
              {children}
            </ThemeProvider>
          </MuiThemeProvider>
        </StylesProvider>
      </JssProvider>
    </div>
  )
}

StylingProvider.propTypes = propTypes

export default StylingProvider

getPageContext.js :

import { SheetsRegistry } from 'jss'
import { createGenerateClassName } from '@material-ui/styles'
import theme from '../theme'

function createPageContext() {
  return {
    theme,
    // This is needed in order to deduplicate the injection of CSS in the page.
    sheetsManager: new Map(),
    // This is needed in order to inject the critical CSS.
    sheetsRegistry: new SheetsRegistry(),
    // The standard class name generator.
    generateClassName: createGenerateClassName(),
  }
}

let pageContext

export default function getPageContext() {
  // Make sure to create a new context for every server-side request so that data
  // isn't shared between connections (which would be bad).
  if (!process.browser) {
    return createPageContext()
  }

  // Reuse context on the client-side.
  if (!pageContext) {
    pageContext = createPageContext()
  }

  return pageContext
}

I hope this will help someone straggling with MaterialUI and Next.js ClassName issue in the future

@amiral-jion it's good to know that it's a bug introduced in one of the new versions :)

Could it be a recent regression linked to a dependency/transitive dependency?

After more investigation, the problem seems to be the following: React randomly? triggers two renders before the hydration. The makeStyles() logic updates the dynamic (not the static ones) style rules at each render, this increments the class names counter twice instead of once => mismatch. @eps1lon is likely on the right path.

For me it was the automatic imports of my IDE after copy pasting components around.
It would do import Typography from '@material-ui/core/Typography/Typography'.
After i fixed those to '@material-ui/core/Typography' the mismatched style warnings where no more.
I hope this helps others.

Have same issue with next configuration:

"@material-ui/core": "^4.8.2",
"@material-ui/styles": "^4.8.2",
"jss": "10.0.2",
"react-jss": "^10.0.3",
"next": "^9.1.6",

I think because of this mismatch I get this css wrong render on SSR rendering:
Screen Shot 2020-01-06 at 10 07 21
Instead of:
Screen Shot 2020-01-06 at 10 08 09

The error for me looks like:

Warning: Prop className did not match. Server: "MuiButton-label makeStyles-buttonLabel-76 makeStyles-buttonLabel-160" Client: "MuiButton-label makeStyles-buttonLabel-76 makeStyles-buttonLabel-158"

Having the same issue as well:

"@material-ui/core": "^4.8.0",                                                                        
"@material-ui/icons": "^4.5.1",                                                                       
"@material-ui/lab": "^4.0.0-alpha.37",                                                                
"@material-ui/styles": "4.7.1",         

Is this a known bug?

Is there an acceptable workaround for this at the moment? This brakes SSR for me which basically makes material-ui inviable because the app requires SSR.

@fullofcaffeine The workaround is not to use dynamic styles. This doesn't impact the core components, it would impact @material-ui/styles that accepts props and the Box, when used with JSS (no emotion or styled-components).

@oliviertassinari Thanks for the prompt reply.

As far as I understand, dynamic styles are pretty core to the mui customization workflow. I haven't paid much attention, but I think I'm using them heavily. Does the workaround you describe require me not to use makeStyles/createStyles on the server?

None of the core components use the dynamic styles yet because we have uncovered too many issues with them. I don't understand your question.

I don't use material, but use next and sc, so it's problem about them.

@oliviertassinari Sorry if I wasn't clear. Perhaps I don't understand exactly what you meant by dynamic styles. Is that the mechanism by which you customize the CSS by means of makeStyles? If so, I use it all over the app to style it. Or are you referring to something else?

It's different, the issue is with https://material-ui.com/styles/basics/#adapting-based-on-props that we don't use with the core components yet because of a bunch of issues we have uncovered.

@oliviertassinari I'm not using this feature, yet I'm still getting a broken layout after the view is hydrated.

Hmm, it seems related to SSR and conditional expressions. If the JSX in question does not have any conditionals, then the styles don't brake when hydration is completed. However, if I add the following line:

        {this.context.loggedIn && <Typography>LOGGED IN!</Typography>}

And reload, the HTML from the server correctly includes "LOGGED_IN", but then after hydrating, the whole style brakes, breaking the layout and the aforementioned error is displayed in the browser console.

Any insights?

Apologies, I finally found out why it was happening. Happens that on the SSR server, the context was not being loaded, so it was actually causing a mismatch of states between client<>server hydration. This was causing the bad class names, for some reason.

@fullofcaffeine Can I know what is the fix for this. Is going back to the older Material version the only fix as of now??

I am getting this,

index.js:1 Warning: Prop `className` did not match. Server: "MuiAvatar-root MuiAvatar-circle makeStyles-avatar-6621 MuiAvatar-colorDefault" Client: "MuiAvatar-root MuiAvatar-circle makeStyles-avatar-2 MuiAvatar-colorDefault"

Also, the fieldset is not adding the little space behind the label unless I force it to hot-replace the component.

Basically https://material-ui.com/getting-started/templates/sign-in/ but with NextJS

Following this article, I managed to make it work but isn't straight forward, I hope it can help someone.
https://medium.com/manato/ssr-with-next-js-styled-components-and-material-ui-b1e88ac11dfa

Any news?

I have the feeling that this issue is going off track. The problem we are discussing is dynamic props usage that can sometimes cause an issue (strict mode issue). However there are a bunch of comments (and likely upvotes) that are unrelated. If you are facing this issue, and not using style functions (e.g only using the components), then you are facing a miss configuration issue. It's not the right place to ask for help. For instance, I was recently helping https://github.com/mui-org/material-ui/issues/18018#issuecomment-576373174 :).

what built in components are also dynamic? Box was mentioned above, are there others or am I misreading that?

I removed strict mode during development mode on the app i'm working on and that helped with this issue. interestingly with strict mode on, I was still getting this with CardMedia component.

Following this article, I managed to make it work but isn't straight forward, I hope it can help someone.
https://medium.com/manato/ssr-with-next-js-styled-components-and-material-ui-b1e88ac11dfa

Thanks, @edgarcheverier!
This works for me after updating the _document.tsx and _app.tsx files.

I am using

    "@material-ui/core": "^4.9.5",
    "@material-ui/icons": "^4.9.1",
    "next": "latest",
    "react": "16.13.0",
    "styled-components": "^5.0.1"

For me it was the automatic imports of my IDE after copy pasting components around.
It would do import Typography from '@material-ui/core/Typography/Typography'.
After i fixed those to '@material-ui/core/Typography' the mismatched style warnings where no more.
I hope this helps others.

This fix helped me 馃憤

I'm having this issue again :(

But now it's related to the server having two rendering passes (AFAIK, that's how it should be) + the className suffix generation logic.

If anyone could have a look, the issue is described here (and here).

EDIT: No, it wasn't related to this either. Check the answer if you're curious

For me it was the automatic imports of my IDE after copy pasting components around.
It would do import Typography from '@material-ui/core/Typography/Typography'.
After i fixed those to '@material-ui/core/Typography' the mismatched style warnings where no more.
I hope this helps others.

this helped me too, it was very tricky to find all imports but it seems like these imports affect the unrelated customized (styled) components on the page....
)im not sure its connected but @oliviertassinari i suggest you to look at this and it might also be the issue in this problem )

Hi @oliviertassinari I am also facing this issue of differnet classnames on server and at client side and I am passing props in many parts of my application. Is there any workaround available or I will have to stop passing props from the entire appication(that will be a very tedious task). Please help.

@oliviertassinari can you please look into it on priority if possible ? I am stuck right now. Will have to update the css from the whole application because of this issue.

@ankit-gupta1307 have tried to check in your whole application if u have imports with duplicate component directory like this example: import Typography from '@material-ui/core/Typography/Typography' ?

Yes @adir1661 I have looked into my application, don't have these kind of import statements.

same here, got the same bug and none of that double folder import.

I'm using dynamic styles .

Can anyone here help ?

Something really unexpected fixed the bug for me, hope this will be the same for everyone.

I replaced const css = sheets.toString(); and <style id="jss-server-side">${css}</style> in HTML by ${sheets.getStyleElement()} which generates the html tag.

After that, I don't have any more problems with dynamic styling in SSR.

Really hope this will fix the bug for all of you.

I have the feeling that this issue is going off track. The problem we are discussing is dynamic props usage that can sometimes cause an issue (strict mode issue). However there are a bunch of comments (and likely upvotes) that are unrelated. If you are facing this issue, and not using style functions (e.g only using the components), then you are facing a miss configuration issue. It's not the right place to ask for help. For instance, I was recently helping #18018 (comment) :).

Can you please explain that issue in detail - why is that happening? AFAIK React.StrictMode only highlights potential issues, but in this thread I see it even breaks the code. How?

AFAIK React.StrictMode only highlights potential issues, but in this thread I see it even breaks the code. How?

It highlights actual issues. It is intended that components inside React.StrictMode break if they're not concurrent safe.

An update, this issue is being resolved in v5 thanks to #22342 and the new @material-ui/styled-engine package.

That's great @oliviertassinari will give it a try

Was this page helpful?
0 / 5 - 0 ratings