React + Material-UI - Warning: Prop className did not match

37,618

Solution 1

The problem is the SSR rendering in Next.js, which produces the style fragment before the page is rendered.

Using Material UI and Next.js (as the author is using), adding a file called _document.js solved the problem.

Adjusted _document.js (as suggested here):

import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@material-ui/styles'; // works with @material-ui/core/styles, if you prefer to use it.
import theme from '../src/theme'; // Adjust here as well

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          {/* Not exactly required, but this is the PWA primary color */}
          <meta name="theme-color" content={theme.palette.primary.main} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (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.
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
  };
};

Solution 2

This problem is related to MUI using dynamic class name which contain an ID. The IDs from the server side rendered CSS are not the same as the client side CSS, hence the mismatch error. A good start is to read the MUI SSR documentation

If you have this problem with nextjs (as I did) follow the example provided by the MUI team, which can be found here: material-ui/examples/nextjs

The most important part is in "examples/nextjs/pages/_app.js":

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

the related ticket can be found here: mui-org/material-ui/issues/15073

what it does, is remove the server side rendered stylesheet and replace it by a new client side rendered one

Solution 3

The issue is the server side generates the class names but style sheets are not automatically included in the HTML. You need to explicitly extract the CSS and append it to the UI for the server side rendered components. The whole process is explained here: https://material-ui.com/guides/server-rendering/

Solution 4

There is one other important, separate issue here: Material UI V4 is not React Strict Mode compatible. Strict mode compatibility is slated for version 5 with the adoption of the Emotion style engine.

Until then, be sure you disable React Strict Mode. If you're using Next.js, this is turned on by default if you've created your app using create-next-app.

// next.config.js
module.exports = {
  reactStrictMode: false, // or remove this line completely
}

Solution 5

I had the same problem with Next.js and styled component, with the transpilation by Babel. Actually, the class names are different on the client and the server side.

Fix it in writing this in your .babelrc :

{
"presets": ["next/babel"],
"plugins": [
    [
      "styled-components",
      { "ssr": true, "displayName": true, "preprocess": false }
    ]
]
}
Share:
37,618
Admin
Author by

Admin

Updated on January 28, 2022

Comments

  • Admin
    Admin over 2 years

    I'm having difficulty with differences between client-side and server-side rendering of styles in Material-UI components due to classNames being assigned differently.

    The classNames are assigned correctly on first loading the page, but after refreshing the page, the classNames no longer match so the component loses its styling. This is the error message I am receiving on the Console:

    Warning: Prop className did not match. Server: "MuiFormControl-root-3 MuiFormControl-marginNormal-4 SearchBar-textField-31" Client: "MuiFormControl-root-3 MuiFormControl-marginNormal-4 SearchBar-textField-2"

    I've followed the Material-UI TextField example docs, and their accompanying Code Sandbox example, but I can't seem to figure out what is causing the difference between the server and client classNames.

    I experienced a similar issue when adding Material-UI Chips with a delete 'x' icon. The 'x' icon rendered with a monstrous 1024px width after refreshing. The same underlying issue being that icon was not receiving the correct class for styling.

    There are a few questions on Stack Overflow addressing why the client and server might render classNames differently (e.g. need to upgrade to @Material-UI/core version ^1.0.0, using a custom server.js, and using Math.random in setState), but none of these apply in my case.

    I don't know enough to tell whether this Github discussion might help, but likely not since they were using a beta version of Material-UI.

    Minimal steps to reproduce:

    Create project folder and start Node server:

    mkdir app
    cd app
    npm init -y
    npm install react react-dom next @material-ui/core
    npm run dev
    

    edit package.json:

    Add to 'scripts': "dev": "next",

    app/pages/index.jsx:

    import Head from "next/head"
    import CssBaseline from "@material-ui/core/CssBaseline"
    import SearchBar from "../components/SearchBar"
    
    const Index = () => (
      <React.Fragment>
        <Head>
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
          />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <meta charSet="utf-8" />
        </Head>
        <CssBaseline />
        <SearchBar />
      </React.Fragment>
    )
    
    export default Index
    

    app/components/SearchBar.jsx:

    import PropTypes from "prop-types"
    import { withStyles } from "@material-ui/core/styles"
    import TextField from "@material-ui/core/TextField"
    
    const styles = (theme) => ({
      container: {
        display: "flex",
        flexWrap: "wrap",
      },
      textField: {
        margin: theme.spacing.unit / 2,
        width: 200,
        border: "2px solid red",
      },
    })
    
    class SearchBar extends React.Component {
      constructor(props) {
        super(props)
        this.state = { value: "" }
        this.handleChange = this.handleChange.bind(this)
        this.handleSubmit = this.handleSubmit.bind(this)
      }
    
      handleChange(event) {
        this.setState({ value: event.target.value })
      }
    
      handleSubmit(event) {
        event.preventDefault()
      }
    
      render() {
        const { classes } = this.props
        return (
          <form
            className={classes.container}
            noValidate
            autoComplete="off"
            onSubmit={this.handleSubmit}
          >
            <TextField
              id="search"
              label="Search"
              type="search"
              placeholder="Search..."
              className={classes.textField}
              value={this.state.value}
              onChange={this.handleChange}
              margin="normal"
            />
          </form>
        )
      }
    }
    
    SearchBar.propTypes = {
      classes: PropTypes.object.isRequired,
    }
    
    export default withStyles(styles)(SearchBar)
    

    Visit page in browser localhost:3000 and see this:

    red border around TextField component

    Refresh the browser and see this:

    TextField component's styles are gone

    Notice that the red border around TextField disappears.

    Relevant Libs:

    • "react": 16.4.0
    • "react-dom": 16.4.0
    • "next": 6.0.3
    • "@material-ui/core": 1.2.0
  • lekhamani
    lekhamani over 4 years
    Hi, I followed exactly the same as described in the documentation. but the styles are different from the server and client and also they are not aligned properly. The console reads that there is a className mismatch. Any pointers to solve the issue would be really helpful. Thanks.
  • Dhana Krishnasamy
    Dhana Krishnasamy over 4 years
    @lekhamani without more info its hard to say what could be the issue. could you add more details?
  • Jamie Hutber
    Jamie Hutber over 4 years
    I do not have styled-components
  • chrisweb
    chrisweb over 4 years
    for nextjs please see my answer below
  • Laode Muhammad Al Fatih
    Laode Muhammad Al Fatih about 4 years
    @JamieHutberI also like that, but I wonder why this way works
  • Mārcis P
    Mārcis P about 4 years
    For me also _document.js was needed from those examples.
  • Admin
    Admin almost 4 years
    Thank you for posting a solution to the problem directly to this thread. I haven't tested this myself, but at a glance it looks equivalent to the solution I eventually landed on.
  • Yury Kozlov
    Yury Kozlov almost 4 years
    @NewbieDev90, The code I posted is actually from styled-components, not mine. What I did is just configured both webpacks (client and server) to use the same environment.
  • joejknowles
    joejknowles over 3 years
    @YuryKozlov but how?
  • Yury Kozlov
    Yury Kozlov over 3 years
    At runtime you may pass it as environment variable: there are multiple ways, depending on what OS you use (windows, linux, etc) and how you start your app (manually or as part of some automation, e.g. docker, kubernetes, etc). In webpack it can be retrieved from environment variable, "mode" prameter in config file or as flag: "webpack -p". webpack.js.org/guides/production/#specify-the-mode
  • Dijiflex
    Dijiflex over 3 years
    I agree with your answer. for those who like video explanations. you can take a look at this video below https://www.youtube.com/watch?v=mtGQe7rTHn8
  • Matt Loye
    Matt Loye over 3 years
    Thousands thanks for this, it did the job.
  • dhellryder
    dhellryder almost 3 years
    This works for me. Thanks a ton for posting this. :)
  • victor.ja
    victor.ja almost 3 years
    This should be the accepted answer for 2021
  • CodeChari
    CodeChari almost 3 years
    you are a savior
  • Admin
    Admin almost 3 years
    NOTE for those reading chrisweb's comment above "for nextjs please see my answer below", I've accepted this answer so now it should read "above".
  • Jatin Hemnani
    Jatin Hemnani almost 3 years
    where is .babelrc file?
  • jvhang
    jvhang over 2 years
    This is the correct answer but for anyone needing a quick fix, one can wrap the MUI component in <NoSSR> to quickly remove it from SSR rendering: v4.mui.com/components/no-ssr/#no-ssr
  • zhongxiao37
    zhongxiao37 over 2 years
    For the guys using react-jss, you could refer to stackoverflow.com/a/62733712/835239.
  • Hrun
    Hrun over 2 years
    @JatinHemnani If you don't have a .babelrc, just create it in the root folder of your project. Here is an example.
  • James
    James over 2 years
    This saved my day. I was searching a solution for this problem I faced in MUI version 5. Mui docs provides scaffold project at github.com/mui-org/material-ui/tree/HEAD/examples/nextjs however, that example needs the above correction to get rid of className mismatch error! As mentioned by the author, everything the return function returns need to be wrapped up inside <StylesProvider generateClassName={generateClassName}>....</StylesProvider>. Thanks @user9019830
  • Journey_Man
    Journey_Man over 2 years
    This fixed the bug I was having with mui v5 as well. You have no idea how appreciative I am of you. I second that motion. They need to update the example to include this. Thank you thank you thank you!
  • spwisner
    spwisner over 2 years
    That did it. You saved the day.
  • Meli
    Meli over 2 years
    I already have this in place but I'm still experiencing the problem.
  • Meli
    Meli over 2 years
    I have this (with useEffect() not componentDidMount() and _document.js and I am still experiencing the problem.
  • Meli
    Meli over 2 years
    Thank you this was the root cause of my problem.
  • Saber
    Saber over 2 years
    @fluggo Thanks!!! How did you find this solution?
  • mrdecemberist
    mrdecemberist about 2 years
    @Arvand Some deep, deep digging.
  • roninMo
    roninMo almost 2 years
    Any way to do this through the next config file instead of babel? I require the default for styled components as well as loading in some of my custom design themes for my projects with next-transpile-modules.