Search code examples
reactjsnext.jsreact-contextdarkmode

useContext state given with undefined in Next.js


I have a dark mode context and I import it in a header to change themes, code below.

code of ThemeContext.tsx

"use client"

import { createContext, useState, ReactNode, useEffect } from "react";

type Theme = {
    darkMode: boolean,
    toggleDarkMode(): void
}

export const ThemeContext = createContext({} as Theme)

export function ThemeProvider({children}: {children: ReactNode}){
    const [darkMode, setDarkMode] = useState(false);

    useEffect(() => {
      const storedMode = localStorage.getItem('darkMode');
      setDarkMode(storedMode === 'true');
    }, []);
  
    function toggleDarkMode() {
      const newMode = !darkMode;
      setDarkMode(newMode);
      localStorage.setItem('darkMode', String(newMode));
    };

    return (
        <ThemeContext.Provider value={{darkMode, toggleDarkMode}}>
            {children}
        </ThemeContext.Provider>
    )
}

When I import it into the header component, it works perfectly, saves it in localStorage and changes the icons according to the theme, Header code below.

code of Header.tsx

"use client"

import Link from "next/link"
import {SunMedium, Moon} from "lucide-react"
import { roboto } from "../fonts"
import {useContext}  from "react"
import { ThemeContext } from "../contexts/ThemeContext"

export default function Header(){
    const {darkMode, toggleDarkMode} = useContext(ThemeContext)

    return(
        <header className="flex justify-between py-7">
            <div>
                <Link href="/" className={`${roboto.className} font-medium text-3xl`}>Compreendendo o cérebro</Link>
            </div>
            <nav className="flex gap-5">
                {darkMode ? <SunMedium className="cursor-pointer" onClick={toggleDarkMode}/> : <Moon className="cursor-pointer" onClick={toggleDarkMode}/>}
                <Link href="/" className="text-xl hover:text-zinc-500">Inicio</Link>
                <Link href="articles" className="text-xl hover:text-zinc-500">Posts</Link>
                <Link href="/references" className="text-xl hover:text-zinc-500">Referências</Link>
            </nav>
        </header>
    )
}

but when I import it into Layout.tsx so that the class changes in the body and I can change the theme colors, darkmode arrives as undefined, but in the header it works and shows true and false perfectly.

code of layout.tsx

"use client"

import { Inter } from 'next/font/google'
import './globals.css'
import { ThemeProvider } from './contexts/ThemeContext'
import Header from './components/Header'
import { ThemeContext } from './contexts/ThemeContext'
import { ReactNode, useContext } from 'react'

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

export default function RootLayout({children,}: {children: ReactNode}) {
  const {darkMode} = useContext(ThemeContext)

  return (
    <ThemeProvider>
      <html lang="pt-br">
          <body className={`mx-64 ${inter.className} ${darkMode ? 'dark' : 'light'}`}>
            <Header/>
            {children}
          </body>
      </html>
    </ThemeProvider> 
  )
}

I would like to know what could be going wrong in this code, or any fix that could be used.


Solution

  • The root layout is a Server Component by default and can not be set to a Client Component. So there is no point in adding "use client" directive at the top.

    refer: https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts

    Hooks or local storage are not accessible from server components. If you need to access your theme information store them in cookies. which can be accessed from the server. eg:

    import { cookies } from 'next/headers'
     
    export default function Page() {
      const cookiesList = cookies()
      const hasCookie = cookiesList.has('theme')
      const theme = cookieStore.get('theme')
      return '...'
    }
    

    https://nextjs.org/docs/app/api-reference/functions/cookies