Closed3

ダークモードの対応ミスによる「Prop 'className' did not match.」

CanoCano
該当コード
const getInitIsDark = () => {
  // クライアントの場合 OS 設定を参照
  if (typeof window !== "undefined") {
    return matchMedia("(prefers-color-scheme: dark)").matches;
  }

  // サーバの場合ライトモード
  return false;
};

const Theme = ({ children }) => {
  const [isDark, setIsDark] = useState(getInitIsDark()); // 原因
  
  // OS 設定が変更されたときの処理とか...

  return (
    <ThemeProvider theme={isDark ? DarkTheme : LightTheme}>
      {children}
    </ThemeProvider>
  );
};

原因はダークモードかどうかの State を初期化する時に matchMedia を使用しているため
ホワイトフラッシュ対策だったが、Next 環境で OS 設定がダークモードだと、サーバサイドとクライアントサイドのテーマの違いによって className に差が生じる

CanoCano

修正方法としては、副作用を伴う処理なのでちゃんと useEffect で行うこと

useLayoutEffect を使用すれば画面に描画される前に実行されるので、ホワイトフラッシュの回避も出来る (知らんかった)

// サーバサイドで `useLayoutEffect` を使用すると警告が出るので抑制
const useIsomorphicLayoutEffect =
  typeof window !== "undefined" ? useLayoutEffect : useEffect;

const Theme = ({ children }) => {
  const [isDark, setIsDark] = useState(false);

  useIsomorphicLayoutEffect(() => {
    const matchIsDark = matchMedia("(prefers-color-scheme: dark)").matches;
    setIsDark(matchIsDark);
  }, []);

  return (
    <ThemeProvider theme={isDark ? DarkTheme : LightTheme}>
      {children}
    </ThemeProvider>
  );
};

Mui では上記を簡単にできる useMediaQuery フックがあるので、今回はこれを使う

const Theme = ({ children }) => {
  const isDark = useMediaQuery("(prefers-color-scheme: dark)");

  return (
    <ThemeProvider theme={isDark ? DarkTheme : LightTheme}>
      {children}
    </ThemeProvider>
  );
};
このスクラップは2021/09/04にクローズされました