skip to Main Content

I am trying to implement dual theme (dark & light) in Next JS using MUI using use context method.

It gives me this error:

 ⨯ srcapppage.tsx (6:50) @ useThemeContext
 ⨯ TypeError: (0 , _providers_theme_context_provider__WEBPACK_IMPORTED_MODULE_1__.useThemeContext) is not a function
    at Home (./src/app/page.tsx:16:117)
    at stringify (<anonymous>)
digest: "3545525166"

Kindly help me to resolve this issue.

My @/theme/index.tsx

'use client';
import { createTheme, responsiveFontSizes } from '@mui/material/styles';
import { lightPalette, darkPalette } from './palette';

export const lightTheme = responsiveFontSizes(createTheme({
    typography: {
        fontFamily: 'var(--font-roboto)',
    },
    palette: lightPalette,
}));

export const darkTheme = responsiveFontSizes(createTheme({
    typography: {
        fontFamily: 'var(--font-roboto)',
    },
    palette: darkPalette
}));

I am trying to use context provider method by implementing in file @/providers/theme-context-provider.tsx:

// @/providers/theme-context-provider.tsx
'use client'; // Required for client-side hooks

import { createContext, useContext, useState, useMemo, useEffect, ReactNode } from 'react';
import { ThemeProvider, CssBaseline } from '@mui/material';
import { lightTheme, darkTheme } from '@/theme'; // Adjusted import path

// Define the shape of the context
interface ThemeContextType {
    toggleTheme: () => void;
    mode: 'light' | 'dark';
}

// Create a context to store theme-related information
const ThemeContext = createContext<ThemeContextType>({
    toggleTheme: () => {},
    mode: 'light', // Default value
});

// Custom hook to use the ThemeContext
export const useThemeContext = () => useContext(ThemeContext);

interface ThemeContextProviderProps {
    children: ReactNode;
}

// Create the provider component that will wrap your app
export const ThemeContextProvider: React.FC<ThemeContextProviderProps> = ({ children }) => {
    // Check for user's preferred color scheme
    const prefersDarkMode = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches;
    const [mode, setMode] = useState<'light' | 'dark'>(prefersDarkMode ? 'dark' : 'light');

    // Sync with system theme changes
    useEffect(() => {
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
        const handleChange = (e: MediaQueryListEvent) => setMode(e.matches ? 'dark' : 'light');
        mediaQuery.addEventListener('change', handleChange);
        return () => mediaQuery.removeEventListener('change', handleChange);
    }, []);

    // Handle manual theme toggle
    const toggleTheme = () => {
        const newMode = mode === 'light' ? 'dark' : 'light';
        setMode(newMode);
        localStorage.setItem('theme', newMode); // Persist the theme in localStorage
    };

    // Memoize the current theme
    const currentTheme = useMemo(() => (mode === 'light' ? lightTheme : darkTheme), [mode]);

    return (
        <ThemeContext.Provider value={{ toggleTheme, mode }}>
            <ThemeProvider theme={currentTheme}>
                <CssBaseline />
                {children}
            </ThemeProvider>
        </ThemeContext.Provider>
    );
};

I used this provider in @/app/layout.tsx:

import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter'
import { ThemeContextProvider } from "@/providers/theme-context-provider";

const geistSans = localFont({
    src: "./fonts/GeistVF.woff",
    variable: "--font-geist-sans",
    weight: "100 900",
});
const geistMono = localFont({
    src: "./fonts/GeistMonoVF.woff",
    variable: "--font-geist-mono",
    weight: "100 900",
});

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="en">
            <body className={`${geistSans.variable} ${geistMono.variable}`}>
                <AppRouterCacheProvider options={{ enableCssLayer: true }}>
                    <ThemeContextProvider>
                        {children}
                    </ThemeContextProvider>
                </AppRouterCacheProvider>
            </body>
        </html>
    );
}

But when i try to use this context in my @/app/page.tsx

// app/page.tsx
import { Box, Button, Typography } from "@mui/material";
import { useThemeContext } from "@/providers/theme-context-provider";

export default function Home() {
    const { toggleTheme, mode } = useThemeContext();

    return (
        <Box padding={2}>
            <Typography variant="h4" gutterBottom>
                Hello, welcome to my Next.js app!
            </Typography>
            <Button variant="contained" color="primary">
                HELLO
            </Button>
            <Button variant="contained" color="primary" onClick={toggleTheme}>
                Toggle to {mode === 'light' ? 'Dark' : 'Light'} Mode
            </Button>
        </Box>
    );
}

2

Answers


    • You don’t need to add 'use client' inside the hook file itself; simply remove 'use client' from @/providers/theme-context-provider.tsx.
    • <ThemeContextProvider> must be used within a Client Component. To achieve this, create a new component named ClientProviders that wraps <ThemeContextProvider> (include 'use client' in ClientProviders).
    • The @/app/page.tsx file should also be a Client Component (include 'use client') because it relies on useThemeContext.
    Login or Signup to reply.
  1. useThemeContext(); should be used in the client page so you need wrap the your button in to client componant … then it will work

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search