Skip to content

Material-ui style getting overwritten when switching light/dark theme using useMediaQuery hook

I was using next.js and material-ui, and changes theme based on user preference. But it seems when switched to light mode, the Styles (using JSS) I set will get overwritten, It only happens when using light mode I even tried to reverse the two theme, but it doesn’t work. After a lot of trying I found when change the system/browser to use the theme different from the one set in the useMediaQuery() hook, the problem will happen. const isInDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); (for instance when set prefers-color-scheme to dark, the light theme will cause Styles to be overwritten.) But I still do not know why this occoured and how to avoid.

Edit: After manually set isInDarkMode = false , The style got overwritten regardless of mode.. so probably useMediaQuery did not cause the problem but actually kinda solve the problem..?

Edit After I tried to deploy this app to vercel, Home page truned fully normal under all color modes, some pages generated by getStaticPaths() still has the problem.

This is tested both on Safari on iOS and Chrome on Windows, and the problem all occours.

Code I used to set Theme (I set it in _app.js):

import Header from "../components/Header";
import React from "react";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";
import "../styles/global.css";

function MyApp({ Component, pageProps }) {
  const isInDarkMode = useMediaQuery("(prefers-color-scheme: dark)");

  const theme = React.useMemo(
    () =>
      createTheme({
        spacing: 8,

        palette: {
          type: isInDarkMode ? "dark" : "light",
          primary: {
            main: isInDarkMode ? "#8A6BBE" : "#6F3381",
          },
          secondary: {
            main: "#CB4042",
          },
          background: {
            default: isInDarkMode ? "#1c1c1c" : "#fffffb",
          },
        },

        typography: {
          h3: {
            fontFamily: "Source Serif Pro, serif",
            fontWeight: "600",
            fontSize: "2.2rem",
            "@media (min-width:600px)": {
              fontSize: "3rem",
            },
          },
        },
      }),
    [isInDarkMode]
  );

  return (
    <>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Header isInDarkMode={isInDarkMode} />
        <Component {...pageProps} />
      </ThemeProvider>
    </>
  );
}

export default MyApp;

(I have tried without useMemo, but did not solve the problem.)

const useStyles = makeStyles({
  root: {
    maxWidth: 345,
    backgroundColor: "rgba(255, 255, 255, 0)",
    border: "1px solid rgba(138, 107, 190, 0.7)",
    borderRadius: "8px",
  },
});

(One example for the Style, The problem will cause all styles to be overwritten include maxWidth, BackgroundColor and border/borderRadius)

Answer

Problem solved! It seems like it is caused by not having the Server Side injected CSS removed. I followed the tutorial Here

For reference these are my _document.js

import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "@material-ui/core/styles";

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head></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(),
    ],
  };
};

and _app.js

import Header from "../components/Header";
import React from "react";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";

import { lightTheme, darkTheme } from "../utils/themes/Theme";
import "../styles/global.css";

function MyApp({ Component, pageProps }) {
  const isInDarkMode = useMediaQuery("(prefers-color-scheme: dark)");

  React.useEffect(() => {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <ThemeProvider theme={isInDarkMode ? darkTheme : lightTheme}>
      <CssBaseline />
      <Header isInDarkMode={isInDarkMode} />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

export default MyApp;

(I changed to create theme in a different file, but it should be optional)