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

时间:2018-06-04 16:41:37

标签: node.js reactjs material-ui nextjs

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

8 个答案:

答案 0 :(得分:3)

问题在于Next.js中的SSR呈现,它会在呈现页面之前生成样式片段。

使用Material UI和Next.js(作者正在使用),添加名为_document.js的文件即可解决此问题。

调整后的_document.jsas 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()],
  };
};

答案 1 :(得分:2)

问题是服务器端会生成类名称,但是样式表不会自动包含在HTML中。您需要显式提取CSS,并将其附加到服务器端渲染组件的UI中。此处说明了整个过程:https://material-ui.com/guides/server-rendering/

答案 2 :(得分:2)

这里还有一个重要的单独问题:Material UI 是 not React Strict Mode compatible。严格模式兼容性预定用于带有 adoption of the Emotion style engine 的版本 5。

在那之前,请确保禁用 React 严格模式。如果您使用 Next.js,除非您在 next.config.js 中启用它,否则默认情况下这是关闭的。

答案 3 :(得分:2)

// 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" }




*/  }

答案 4 :(得分:1)

我对Next.js和样式化组件也遇到了同样的问题,Babel进行了转译。实际上,客户端和服务器端的类名是不同的。

通过在.babelrc文件中编写此内容来解决此问题:

{
"presets": ["next/babel"],
"plugins": [
    [
      "styled-components",
      { "ssr": true, "displayName": true, "preprocess": false }
    ]
]

}

答案 5 :(得分:0)

此问题与使用包含ID的动态类名的MUI有关。服务器端渲染的CSS的ID与客户端CSS的ID不同,因此会出现不匹配错误。一个好的开始是阅读MUI SSR documentation

如果您像nextjs那样遇到问题(就像我一样),请遵循MUI团队提供的示例,可以在此处找到:material-ui/examples/nextjs

最重要的部分是“ 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);
    }
  }

相关票证可以在这里找到:mui-org/material-ui/issues/15073

它的作用是删除服务器端渲染的样式表,并用新的客户端渲染样式表替换

答案 6 :(得分:0)

在我的情况下,发生此问题的原因是,webpack用于客户端代码和服务器端的编译模式不同:客户端的捆绑包是由webpack使用“生产”模式生成的,而服务器则从针对“发展”。这在generateAndInjectStyles()中的样式组件中创建了一个不同的“ className”哈希:

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

所以我的解决方法只是调整Webpack模式。

答案 7 :(得分:0)

我在客户端和服务器上使用不同的className时遇到问题。我正在使用 React Material-UI makeStyles SSR(服务器端渲染)。 错误是:

idxs

我花了几个小时才发现在 webpack模式中客户端和服务器存在差异。 Warning: Prop `className` did not match. Server: "jss3" Client: "App-colNav-3" 中的脚本为:

package.json

将两个都更改为 "devServer": "webpack --config webpack.server.config.js --mode=production --watch", "devClient": "webpack --mode=development --watch", 模式后,问题已解决:)

development