pacocoursey / next-themes

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing
https://next-themes-example.vercel.app/
MIT License
5.1k stars 185 forks source link

[Bug]: Example in README > Examples > Avoid Hydration Mismatch > Images does not work on initial load #311

Open davidde opened 1 month ago

davidde commented 1 month ago

What happened?

This code from the README causes Warning: Prop 'loading' did not match. Server: "null" Client: "lazy":

import Image from 'next/image'
import { useTheme } from 'next-themes'

function ThemedImage() {
  const { resolvedTheme } = useTheme()
  let src

  switch (resolvedTheme) {
    case 'light':
      src = '/light.png'
      break
    case 'dark':
      src = '/dark.png'
      break
    default:
      src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
      break
  }

  return <Image src={src} width={400} height={400} />
}

export default ThemedImage

It does not render the image on initial load or page refresh.

I noticed it works with the useEffect/setMounted hack, but this causes other problems like layout shift. Is there a fix for this?

Version

^0.3.0

What browsers are you seeing the problem on?

Firefox, Chrome

90PabloRomero commented 3 weeks ago

try to set same size container to the image element to avoid shifting try this approach instead

'use client'
import { useTheme } from "next-themes";
import type { ImageProps } from "next/image";
import Image from "next/image";
import { useEffect, useState } from "react";

type Props = Omit<ImageProps, "src" | "priority" | "loading"> & {
  srcLight: string;
  srcDark: string;
};

const ThemedImage = (props: Props) => {
  const { resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);
  const { srcLight, srcDark, ...rest } = props;

  let srcImage;

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

  if (!mounted) {
    return null;
  }

  switch (resolvedTheme) {
    case "light":
      srcImage = srcLight;
      break;
    case "dark":
      srcImage = srcDark;
      break;
    default:
      srcImage = srcLight;
      break;
  }

  return (
    <Image
      src={srcImage}
      {...rest}
    />
  );
};

export default ThemedImage;