use-dark-mode icon indicating copy to clipboard operation
use-dark-mode copied to clipboard

Dark mode flashes in Next.js even with noflash.js

Open markdost opened this issue 4 years ago • 9 comments

The dark mode on my Next.js app still flashes even with adding noflash.js. I have made a dark mode component like so:

import useDarkMode from "use-dark-mode";

import { FiMoon } from "react-icons/fi";

const DarkModeToggle = () => {
  const darkMode = useDarkMode(false);

  return (
    <div className="custom-control custom-switch">
      <input
        type="checkbox"
        className="custom-control-input"
        id="darkmodeSwitch"
        onChange={darkMode.toggle}
        checked={darkMode.value}
        onClick={darkMode.disable}
      />
      <label className="custom-control-label" htmlFor="darkmodeSwitch">
        <FiMoon />
      </label>
    </div>
  );
};

export default DarkModeToggle;

Added noflash.js to the public folder. Then on _document.js

import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <script src="noflash.js" />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

In my SCSS folder I created a simple _dark-mode.scss and added styles for dark mode in there e.g.:

body.light-mode {
  transition: background-color 0.3s ease;
}
body.dark-mode {
  background-color: #121212;
  color: #f5f5f5;
  transition: background-color 0.3s ease;
  img {
    opacity: 0.8;
  }
}

dark_mode_flash As you can see it flashes and there's also the toggle/switch that is not persistent.

markdost avatar Jul 01 '21 08:07 markdost

Hey there, You might try the dark mode solution used in https://jamstackthemes.dev/theme/nextjs-tailwind-starter-blog/ (i.e. next-themes with dark mode as a theme). I was able to get the behavior I wanted (autodetection of system theme, local storage of preference, etc.) in this project https://github.com/DoctorDerek/steven-terner.github.io https://steventerner.com using a useEffect() handler.

djD-REK avatar Jul 01 '21 18:07 djD-REK

Hey, I've seen next-themes before and might give that a go, since I might be a nitpicker here but I do not like having the flash.

markdost avatar Jul 01 '21 19:07 markdost

@djD-REK Thanks again for your recommendation, it works great with no flash! Do you have any idea how to apply a toggle to an input like:

<input
  type="checkbox"
  className="custom-control-input"
  id="darkmodeSwitch"
/>

The example of next-themes uses 2 buttons to do the switch.

markdost avatar Jul 02 '21 03:07 markdost

Hey @markdost yeah I got the behavior you want using a useState and useEffect combination if you check out the demo I sent you. It's a little convoluted because of the animation involved, but it should be pretty clear. Let me know if it's not 😄

DoctorDerek avatar Jul 02 '21 18:07 DoctorDerek

@DoctorDerek Yes I got it working now in the end by doing:

const DarkModeToggle = () => {
  const [mounted, setMounted] = useState(false);
  const { theme, setTheme } = useTheme();

  // When mounted on client, now we can show the UI
  useEffect(() => setMounted(true), []);

  if (!mounted) return null;

  return (
    <div className="custom-control custom-switch">
      <input
        type="checkbox"
        className="custom-control-input"
        id="darkmodeSwitch"
        onChange={() => setTheme(theme === "dark" ? "light" : "dark")}
        checked={theme === "dark"}
      />
      <label className="custom-control-label" htmlFor="darkmodeSwitch">
        <FiMoon />
      </label>
    </div>
  );
};

markdost avatar Jul 02 '21 19:07 markdost

Looks like a nice solution, thanks for sharing!

djD-REK avatar Jul 02 '21 19:07 djD-REK

The problem with useEffect is that you lose SSR, causing SEO problems

okalil avatar Jul 07 '21 01:07 okalil

Hmm, that's a good point. I thought it's just a thematic thing, and SEO only has to do with the content, so using useEffect after Next hydrates to update the dark mode wouldn't affect it. And you're right, you can't have pure server side rendering with it.

djD-REK avatar Jul 07 '21 02:07 djD-REK

@okalil I see, so it would cause problems when doing SSR. Keep in mind I switched from this library to next-themes, which might still cause the same problem if doing SSR. The example I gave at https://github.com/donavon/use-dark-mode/issues/83#issuecomment-873220966 is with next-themes.

markdost avatar Jul 07 '21 08:07 markdost