-->

Material UI: Styles flicker in and disappear

2020-07-22 17:10发布

问题:

Styles appear for maybe 50ms and disappear in the below code in this SSR app. I'm curious what could be causing that.

// This component is a child of index.tsx in the /pages folder
    <Button
      color="primary"
      variant="outlined"
      size="large"
    >Test Button</Button>

After the styles disappear a plain HTML button is left.

I believe Next.js is causing this. I checked the Next.js file and have added the next/babel loader to .babelrc. Other than that I only saw this other relevant change. This is in /pages/_document.js:


MyDocument.getInitialProps = async ctx => {
  const sheets = new MuiServerStyleSheets();
  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()],
  };
};

Things done to attempt to resolve

  1. Restart server

No change to issue.

  1. Force refresh Chrome 78 (CTRL + F5)

No change.

Theory

I think there is a server side problem. Client and server should be on the same machine, localhost. That would explain the correct initial result (client initial UI) then being overridden by a server update.

Update 1

Forgot to mention that I did update /pages/_app.js too. Here's the updated portion:

class MyApp extends App {
  componentDidMount() {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles && "parentElement" in jssStyles) {
      (jssStyles.parentElement as HTMLElement).removeChild(jssStyles);
    }
  }

回答1:

The root cause of this error for me, was that the classes generated during server side rendering of the document, don't match the styles that are generated after hydration.

One way to fix this, is to force a rerender after hidration.

One way to do this, is to update the key prop on your component.

// inside your component
const [key, setKey] = React.useState(0);

React.useEffect(() => {
  setKey(1);
}, []);

return (<MyComponent key={}key />)

My full _app.tsx file:

import React from 'react';
import {
  ThemeProvider,
  createGenerateClassName,
  StylesProvider
} from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';

import { darkTheme } from '../theme';

const generateClassName = createGenerateClassName({
  productionPrefix: 'myclasses-'
});

export default function MyApp(props) {
  const { Component, pageProps } = props;

  const [key, setKey] = React.useState(0);

  React.useEffect(() => {
    setKey(1);
  }, []);

  React.useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <StylesProvider key={key} generateClassName={generateClassName}>
      <React.Fragment>
        <ThemeProvider theme={darkTheme}>
          <CssBaseline />
          <Component {...pageProps} />
        </ThemeProvider>
      </React.Fragment>
    </StylesProvider>
  );
}


回答2:

Tentatively solved by commenting out the code to remove the server jssStyles

class MyApp extends App {
  // componentDidMount() {
  //   // Remove the server-side injected CSS.
  //   const jssStyles = document.querySelector('#jss-server-side');
  //   if (jssStyles && "parentElement" in jssStyles) {
  //     (jssStyles.parentElement as HTMLElement).removeChild(jssStyles);
  //   }
  // }

If anyone knows why this code is included in the example, please comment. There must be a reason for it. https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_app.js