React + Material-UI - Warning: Prop className did not match
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 }
]
]
}
Admin
Updated on January 28, 2022Comments
-
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 over 4 yearsHi, 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 over 4 years@lekhamani without more info its hard to say what could be the issue. could you add more details?
-
Jamie Hutber over 4 yearsI do not have styled-components
-
chrisweb over 4 yearsfor nextjs please see my answer below
-
Laode Muhammad Al Fatih about 4 years@JamieHutberI also like that, but I wonder why this way works
-
Mārcis P about 4 yearsFor me also _document.js was needed from those examples.
-
Admin almost 4 yearsThank 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 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 over 3 years@YuryKozlov but how?
-
Yury Kozlov over 3 yearsAt 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 over 3 yearsI 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 over 3 yearsThousands thanks for this, it did the job.
-
dhellryder almost 3 yearsThis works for me. Thanks a ton for posting this. :)
-
victor.ja almost 3 yearsThis should be the accepted answer for 2021
-
CodeChari almost 3 yearsyou are a savior
-
Admin almost 3 yearsNOTE 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 almost 3 yearswhere is .babelrc file?
-
jvhang over 2 yearsThis 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 over 2 yearsFor the guys using
react-jss
, you could refer to stackoverflow.com/a/62733712/835239. -
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 over 2 yearsThis 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 over 2 yearsThis 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 over 2 yearsThat did it. You saved the day.
-
Meli over 2 yearsI already have this in place but I'm still experiencing the problem.
-
Meli over 2 yearsI have this (with useEffect() not componentDidMount() and _document.js and I am still experiencing the problem.
-
Meli over 2 yearsThank you this was the root cause of my problem.
-
Saber over 2 years@fluggo Thanks!!! How did you find this solution?
-
mrdecemberist about 2 years@Arvand Some deep, deep digging.
-
roninMo almost 2 yearsAny 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.