🎨
next.js 14 App Router + MUI dark/light モード切り替え実装 server/client コンポーネント
ThemeContext.tsxを作る
dark/light モード切り替えのためのそれぞれのpaletteをlightPaletteとdarkPaletteに定義し、その他のtheme定義はthemeOptionsで定義した前提で
// src/contexts/ThemeContext.tsx
import { createContext, useMemo, useState } from 'react'
import { createTheme } from '@mui/material/styles'
import {
themeOptions,
lightPalette,
darkPalette,
} from '@/assets/styles/customTheme'
export const ColorModeContext = createContext({
toggleColorMode: () => {},
})
export function useThemeContent() {
const [mode, setMode] = useState<'light' | 'dark'>('dark')
const colorMode = {
toggleColorMode: () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'))
},
}
const themePalette = mode === 'dark' ? darkPalette : lightPalette
const theme = useMemo(
() => createTheme({ palette: { mode, ...themePalette }, ...themeOptions }),
[mode]
)
return { colorMode, theme }
}
layout.tsxのなかでContext.Providerでラップする
// src/app/(application)/app/layout.tsx
'use client'
import { ThemeProvider } from '@mui/material/styles'
import CssBaseline from '@mui/material/CssBaseline'
import { ColorModeContext, useThemeContent } from '@/contexts/ThemeContext'
export default function AppLayout({ children }: { children: React.ReactNode }) {
const { colorMode, theme } = useThemeContent()
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</ColorModeContext.Provider>
)
}
ラッパーされるコンポーネント内でテーマを切り替え
// src/app/(application)/app/_lib/test-toggle.tsx
import { useContext } from 'react'
import { useTheme } from '@mui/material'
import IconButton from '@mui/material/IconButton'
import Brightness4Icon from '@mui/icons-material/Brightness4'
import Brightness7Icon from '@mui/icons-material/Brightness7'
import { ColorModeContext } from '@/contexts/ThemeContext'
export default function ToggleTheme() {
const colorMode = useContext(ColorModeContext)
const theme = useTheme()
return (
<IconButton
sx={{ ml: 1 }}
onClick={colorMode.toggleColorMode}
color='inherit'
>
{theme.palette.mode === 'dark' ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
)
}
最初はuseThemeContentからthemeをインポートしてひたすら動かない(新しいthemeになっちゃったっぽい)
→ themeはmuiからインポートしたやつを使う
import { useTheme } from '@mui/material'
export default function HogeComponent() {
const theme = useTheme()
return (
<div>
{JSON.stringify(theme.palette)}
</div>
)
}
layout.tsxとpage.tsxのサーバーサイド・クライアントサイド問題?
theme切り替えのuseContextがlayout.tsxに入ってるため、layout.tsxはクライアントサイド
そうするとpage.tsxはcookiesなどからユーザー情報取得したりするのでpage.tsxはサーバサイド
イメージとしてlayout.tsxはpage.tsxの親のようなもので、layout.tsxに'use client'しているのに、page.tsx内のcookies処理はなぜ怒られないのかよくわからなかったが...とりあえずlayoutがクライアントサイドでpageがサーバーサイドで落ち着きそう
Discussion