在 Next.js 中设置主题提供程序时出现“警告:Prop `className` 不匹配”

Mat*_*ori 6 javascript typescript reactjs next.js

我正在尝试使用 Context API 创建一个主题提供程序来设置应用程序主题,这只是一个classNameon body

上下文非常简单。在主题状态的惰性初始化器上,首先,我检查主题是否打开localStorage。如果主题不在本地存储中,那么我会根据用户系统首选项设置主题。

'use client'

import { createContext, useEffect, useState } from 'react'
import {
  type ThemeContextData,
  type ThemeProviderProps,
  type Themes
} from './types'
import { darkTheme, lightTheme } from '@/styles/themes.css'

export const ThemeContext = createContext<ThemeContextData>(
  {} as ThemeContextData
)

export function ThemeProvider({
  children,
  className
}: ThemeProviderProps): JSX.Element {
  const [theme, setTheme] = useState<Themes>(() => {
    if (typeof window !== 'undefined') {
      const isThemeOnLocalStorage = window.localStorage.getItem(
        '@matheussartori/theme'
      )

      if (isThemeOnLocalStorage) {
        return isThemeOnLocalStorage as Themes
      }

      const isSystemThemeDark = window.matchMedia(
        '(prefers-color-scheme: dark)'
      ).matches

      if (isSystemThemeDark) {
        return 'dark'
      }
    }

    return 'light'
  })

  useEffect(() => {
    if (typeof window !== 'undefined') {
      window.localStorage.setItem('@matheussartori/theme', theme)
    }
  }, [theme])

  const themeClassNames = {
    light: lightTheme,
    dark: darkTheme
  }

  const classNames = className
    ? `${themeClassNames[theme]} ${className}`
    : themeClassNames[theme]

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <body className={classNames}>{children}</body>
    </ThemeContext.Provider>
  )
}
Run Code Online (Sandbox Code Playgroud)

在我的中layout.tsx,我也得到了以下代码:

import { Inter } from 'next/font/google'

import '@/styles/global.css'
import { ThemeProvider } from '@/contexts/themes/ThemeProvider'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'lorem',
  description:
    'lorem'
}

export default function RootLayout({
  children
}: {
  children: React.ReactNode
}): JSX.Element {
  return (
    <html lang="en">
      <ThemeProvider className={`${inter.className}`}>{children}</ThemeProvider>
    </html>
  )
}
Run Code Online (Sandbox Code Playgroud)

发生的问题可能是由于 ThemeProvider 上的服务器渲染造成的。浏览器中显示的错误是:

警告:道具className不匹配。服务器:“themes_lightTheme__1ra6bom9 __className_0ec1f4”客户端:“themes_darkTheme__1ra6bom0 __className_0ec1f4”

由于服务器不知道window/ localStorage,显然,在补水时,服务器说主题必须是浅色的,但它是深色的。

有没有更好的方法来实现这种方法?

You*_*mar 2

客户端和服务器上的初始渲染应该相同。每当您违反此规则时,您都会收到水合localStorage错误,就像您的情况一样,服务器上不会出现水合错误,但它会出现在浏览器上,从而导致不同的初始状态。

您可以在以下内容中移动实例化主题的逻辑useEffect

"use client";

import { createContext, useEffect, useState } from "react";
import { type ThemeContextData, type ThemeProviderProps, type Themes } from "./types";
import { darkTheme, lightTheme } from "@/styles/themes.css";

export const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData);

export function ThemeProvider({ children, className }: ThemeProviderProps): JSX.Element {
  const [theme, setTheme] = useState<Themes | null>(null);

  useEffect(() => {
    const isThemeOnLocalStorage = window.localStorage.getItem("@matheussartori/theme");

    if (isThemeOnLocalStorage) {
      setTheme(isThemeOnLocalStorage);
      return;
    }

    const isSystemThemeDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

    if (isSystemThemeDark) {
      setTheme("dark");
      return;
    }

    setTheme("light");
  }, []);

  useEffect(() => {
    if (theme) {
      window.localStorage.setItem("@matheussartori/theme", theme);
    }
  }, [theme]);

  const themeClassNames = {
    light: lightTheme,
    dark: darkTheme,
  };

  const classNames = className ? `${themeClassNames[theme]} ${className}` : themeClassNames[theme];

  // This is so you don't show anything before the theme is fully set. You could set a loader or something, or even remove the if statement
  if (!theme) {
    return null;
  }

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <body className={classNames}>{children}</body>
    </ThemeContext.Provider>
  );
}
Run Code Online (Sandbox Code Playgroud)