我在 Next.js Web 应用程序中使用 MUI 在系统、浅色和深色模式之间切换。我通过将所选主题保存在本地存储中来使其在会话之间保持不变。我已经在设置模式的下拉菜单中放置了更改 Web 应用程序主题的功能,因此可以通过
useContext
来更改主题。我遇到的问题是,如果用户选择的主题是“系统”主题(如果您的系统是深色模式)或“深色”主题,则主题在我的所有组件中都不会持久。此外,如果我的主题在初始加载时不是“light”主题或从“light”主题切换到其他主题之一时,我也会收到此错误:
Warning: Prop `className` did not match. Server: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-134qg7o-MuiButtonBase-root-MuiIconButton-root" Client: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-6pxnsq-MuiButtonBase-root-MuiIconButton-root
经过一番搜索,我最初以为这是因为我没有在
CssBaseline
标签中使用 ThemeProvider
,但是,添加它后,它似乎只会让事情变得更糟,并且错误仍然存在。我将在下面的屏幕截图中展示不同的行为:
在“系统”或“暗”模式下的预期行为(
CssBaseline
标签之间没有ThemeProvider
:
在“系统”或“暗”模式下加载时没有
CssBaseline
的实际行为:
使用
CssBaseline
处于“系统”或“暗”模式时的加载行为:
下面列出的是我的上下文代码以及如何从系统获取主题、如何将其存储在本地存储中以及如何切换它:
const ThemeContext = createContext()
const useThemeContext = () => useContext(ThemeContext)
export const ThemeModeProviderComponent = ({ children }) => {
const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
const [selectedTheme, setSelectedTheme] = useState(() =>
typeof localStorage !== 'undefined' && localStorage.getItem('theme') || 'system'
)
const themes = {
light: createTheme({
palette: {
mode: 'light',
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
}),
dark: createTheme({
palette: {
mode: 'dark',
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
}),
system: createTheme({
palette: {
mode: systemTheme,
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
})
}
useEffect(() => {
if (selectedTheme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
themes.system.palette.mode = systemTheme
}
}, [selectedTheme, themes.system])
const handleThemeChange = (event) => {
const { value } = event.target
setSelectedTheme(value)
localStorage.setItem('theme', value)
}
return (
<ThemeContext.Provider value={{ selectedTheme, handleThemeChange }}>
<ThemeProvider theme={themes[selectedTheme]}>
<CssBaseline enableColorScheme/>
{children}
</ThemeProvider>
</ThemeContext.Provider>
)
}
export const ThemeSelector = () => {
const { selectedTheme, handleThemeChange } = useThemeContext()
if (typeof window === 'undefined') return null
return (
<FormControl>
<Select
value={selectedTheme}
onChange={handleThemeChange}
>
<MenuItem value='system'>System</MenuItem>
<MenuItem value='dark'>Dark</MenuItem>
<MenuItem value='light'>Light</MenuItem>
</Select>
</FormControl>
)
}
另一种可能性是,由于我处理抽屉显示和隐藏的方式,它可能会影响风格。我基本上从 MUI 网站here复制了持久抽屉示例,但我认为情况并非如此,正如我们在图像中看到的那样,它在一定程度上工作正常。
这就是我能够弄清楚的方法。我的问题是我试图在初始加载/安装之前从本地存储太快地设置主题,这给我带来了错误,并且如果最初不是轻型模式,某些组件将无法正确使用主题。以下是更正后的代码:
import { CssBaseline, MenuItem, Select, ThemeProvider, createTheme, useMediaQuery } from "@mui/material";
import { createContext, useContext, useEffect, useMemo, useState } from "react"
const ThemeModeContext = createContext()
const useThemeContext = () => useContext(ThemeModeContext)
export const ThemeModeProvider = ({ children }) => {
//Check system preference for dark mode
const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = useState('light')
//Check local storage for theme preference on initial load
useEffect(() => {
if (typeof window !== 'undefined') {
setMode(window.localStorage.getItem('theme') || 'light')
}
}, [])
//Toggle theme mode function
const toggleThemeMode = (event) => {
const newMode = event.target.value
window.localStorage.setItem('theme', newMode)
setMode(newMode)
}
//Create theme based on color mode state
const theme = useMemo(() => (
createTheme({
palette: {
mode: mode === 'dark' || (mode === 'auto' && isDarkMode) ? 'dark' : 'light'
}
})
), [mode, isDarkMode])
//Value for the ThemeModeContext.Provider
const themeModeValue = useMemo(() => ({ mode, toggleThemeMode }), [mode, toggleThemeMode])
return (
<ThemeModeContext.Provider value={themeModeValue}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</ThemeModeContext.Provider>
)
}
export const ThemeSelector = () => {
const { mode, toggleThemeMode } = useThemeContext()
return (
<Select
value={mode}
onChange={(e) => toggleThemeMode(e)}
size='small'
>
<MenuItem value='dark'>Dark</MenuItem>
<MenuItem value='light'>Light</MenuItem>
</Select>
)
}