85

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
David
  • 953
  • 1
  • 6
  • 8
  • Did you get anywhere with solving this issue? – Dhana Krishnasamy Sep 21 '18 at 10:12
  • 1
    @DhanaKrishnasamy - yes, [these MUI docs](https://material-ui.com/guides/server-rendering/) explain what to do to fix the issue. I'm pretty new to web dev so I didn't understand the MUI docs. I ended up following the first chapter of [builderbook](https://builderbook.org/) to integrate client and server rendering of MUI. Note that you can view the builderbook code on [github](https://github.com/builderbook/builderbook/tree/master/boilerplate) for free - I ended up buying the book which cost me $20 and followed the instructions (this saved me at least a day, maybe more). – David Sep 22 '18 at 12:53

15 Answers15

73

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()],
  };
};
Braden Steffaniak
  • 2,053
  • 3
  • 25
  • 37
Leonel Sanches da Silva
  • 6,972
  • 9
  • 46
  • 66
26

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

chrisweb
  • 1,428
  • 19
  • 25
21

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/

Dhana Krishnasamy
  • 2,126
  • 17
  • 36
  • 1
    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. – lekhamani Nov 18 '19 at 23:42
  • @lekhamani without more info its hard to say what could be the issue. could you add more details? – Dhana Krishnasamy Nov 20 '19 at 20:45
  • 3
    for nextjs please see my answer below – chrisweb Dec 29 '19 at 16:54
  • 2
    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](https://www.youtube.com/watch?v=mtGQe7rTHn8) – Dijiflex Jan 15 '21 at 21:40
  • 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". – David Jul 27 '21 at 22:57
  • 1
    This is the correct answer but for anyone needing a quick fix, one can wrap the MUI component in to quickly remove it from SSR rendering: https://v4.mui.com/components/no-ssr/#no-ssr – jvhang Sep 20 '21 at 22:45
16

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
}

Martin Devillers
  • 17,293
  • 5
  • 46
  • 88
mrdecemberist
  • 2,631
  • 2
  • 20
  • 23
14

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 }
    ]
]
}
Saber
  • 5,150
  • 4
  • 31
  • 43
pom421
  • 1,731
  • 19
  • 38
12

I met this problem on Material-ui V5. The solution to fix this problem is to make sure that class name generator needs to behave identically on the server and on the client. so adding the code below in your _app.js:

import { StylesProvider, createGenerateClassName } from '@mui/styles';

const generateClassName = createGenerateClassName({
  productionPrefix: 'c',
});

export default function MyApp(props) {
  return <StylesProvider generateClassName={generateClassName}>...</StylesProvider>;
}
Edward Lee
  • 693
  • 1
  • 7
  • 14
  • 1
    This saved my day. I was searching a solution for this problem I faced in MUI version 5. Mui docs provides scaffold project at https://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 ..... Thanks @user9019830 – James Oct 22 '21 at 15:45
  • 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! – Journey_Man Oct 25 '21 at 00:43
  • But i'm not using that separate package `@mui/styles`, I'm using `@mui/material` , how to solve in this case? – Md. A. Apu Sep 25 '22 at 16:43
5

// 1 . Warning: prop classname did not match. Material ui   with   React  Next.js

// 2 . Use your customization  css here
const useStyles = makeStyles((theme) => ({

    root: {
        flexGrow: 1,
    },

    title: {
        flexGrow: 1,
    },
    my_examle_classssss: {
        with: "100%"
    }

}));


// 3 . Here my Component    
const My_Example_Function = () => {

    const classes = useStyles();

    return (
        <div className={classes.root}>
            <Container>
                <Examle_Component>    {/*  !!! Examle_Component  -->  MuiExamle_Component*/}

                </Examle_Component>
            </Container>
        </div>
    );
}

export default My_Example_Function


// 4. Add  name parameter to the makeStyles function   

const useStyles = makeStyles((theme) => ({

    root: {
        flexGrow: 1,
    },

    title: {
        flexGrow: 1,
    },
    my_examle_classssss: {
        with: "100%"
    },
}), { name: "MuiExamle_ComponentiAppBar" });  

{/* this is the parameter you need to add     { name: "MuiExamle_ComponentiAppBar" } */ }


{/* The problem will probably be resolved     if the name parameter matches the first className in the Warning:  you recive..    


EXAMPLE :

    Warning: Prop `className` did not match. 
    Server: "MuiSvgIcon-root makeStyles-root-98" 
    Client: "MuiSvgIcon-root makeStyles-root-1"


The name parameter will be like this   { name: "MuiSvgIcon" }




*/  }
3

I like to share this mismatching case:

next-dev.js?3515:32 Warning: Prop className did not match. Server: "MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12 MuiSwitch-switchBase MuiSwitch-colorSecondary" Client: "MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12 MuiSwitch-switchBase MuiSwitch-colorSecondary PrivateSwitchBase-checked-13 Mui-checked"

On client there are two more classes which means that the behavior on client-side is different. In this case, this component shouldn't render on server-side. The solution is to dynamically render this component:

export default dynamic(() => Promise.resolve(TheComponent), { ssr: false });
juliomalves
  • 42,130
  • 20
  • 150
  • 146
Edward Lee
  • 693
  • 1
  • 7
  • 14
1

I had a problem with different classNames for client and server. I was using React, Material-UI, makeStyles and SSR (server-side rendering). The error was:

Warning: Prop `className` did not match. Server: "jss3" Client: "App-colNav-3"

I spent several hours before I figured out that I had discrepancy in webpack mode for client and server. The scripts in package.json were:

    "devServer": "webpack --config webpack.server.config.js --mode=production --watch",
    "devClient": "webpack --mode=development --watch",

After I changed both to have development mode, the problem was solved :)

    "devServer": "webpack --config webpack.server.config.js --mode=development --watch",
    "devClient": "webpack --mode=development --watch",
Michael Klishevich
  • 1,774
  • 1
  • 17
  • 17
1

You can add the name in anywhere you use makeStyles, like this:

const useStyles = makeStyles({
  card: {
    backgroundColor: "#f7f7f7",
    width: "33%",
  },
  title: {
    color: "#0ab5db",
    fontWeight: "bold",
  },
  description: {
    fontSize: "1em"
  }
}, { name: "MuiExample_Component" });

I am not sure how it works, but I found it here: Warning: Prop `className` did not match ~ Material UI css arbitrarily breaks on reload

gion_13
  • 41,171
  • 10
  • 96
  • 108
Dustin
  • 11
  • 1
1

If somebody is still struggling even after trying above solutions, Try this

  • If you have used noSsr prop in any of your components or theme, then remove it.

I had the following config in mui theme object, which was causing this problem.

import { createTheme, responsiveFontSizes } from "@mui/material/styles";
let theme = createTheme({
  components: {
    MuiUseMediaQuery: {
      defaultProps: {
        noSsr: true,
      },
    },
  },
  palette: {
    mode: "light",
    common: {
      black: "#000",
      white: "#fff",
    },
    primary: {
      main: "#131921",
      contrastText: "#fff",
    },
    secondary: {
      main: "#fb6a02",
      contrastText: "#fff",
    }
   }
})
  • RemovingnoSSr fixed all of the issues in my app including style mismatch between client and server.
Firoj Siddiki
  • 1,649
  • 1
  • 20
  • 22
1

The problem is cause by Nextjs server side rendering. In order to solve I do as following:

  1. Make a component to detect whether is it from Client side
import { useState, useEffect } from "react";

interface ClientOnlyProps {}

// @ts-ignore
const ClientOnly = ({ children }) => {
  const [mounted, setMounted] = useState<boolean>(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  return mounted ? children : null;
};

export default ClientOnly;

  1. Wrap my page component using ClientOnly component
export default function App() {
  return (
    <ClientOnly>
      <MyOwnPageComponent>
    </ClientOnly>
  );
}

So the idea is, if it is client side then only render the component on the page. Therefore if current rendering is from Client side, render <MyOwnPageComponent>, else render nothing

ken
  • 2,426
  • 5
  • 43
  • 98
0

In my case the issue happened because of different compilation modes of webpack for client-side code and server-side: client's bundle was generated by webpack using "production" mode, while server ran some SSR code from a package optimized for "development". This created a different "className" hash in styled-components in generateAndInjectStyles():

if (process.env.NODE_ENV !== 'production') dynamicHash = phash(dynamicHash, partRule + i);

So my fix was just to align the webpack modes.

Yury Kozlov
  • 1,286
  • 1
  • 11
  • 9
  • @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. – Yury Kozlov Jul 13 '20 at 16:24
  • @YuryKozlov but how? – joejknowles Aug 15 '20 at 09:45
  • 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". https://webpack.js.org/guides/production/#specify-the-mode – Yury Kozlov Aug 16 '20 at 16:12
0

@Leonel Sanches da Silva's answer didn't work for me, as @material-ui/styles is deprecated, but using a snippet I found for another (non-material UI) project seems to have worked just fine for me:

Hat tip to Raul Sanchez on dev.to for the answer to this one.

Next doesn't fetch styled-components styles on the server, to do that you need to add this page to pages/_document.js:

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
}

This code may update, so check Next's styled-components example for the latest.

fredrivett
  • 5,419
  • 3
  • 35
  • 48
-1

I'm also using NextJS + MUI v5 and I ran into this exact error right after merging Git branches. I suspect the merge corrupted something in the cache. I deleted the contents of .next/ and restarted the dev server and the error went away.

emersonthis
  • 32,822
  • 59
  • 210
  • 375