Theme Context in SSR?
Unanswered
Nile Crocodile posted this in #help-forum
Nile CrocodileOP
Hi everyone so I'm really new with the App Router, I learned some of it with a 1h youtube tutorial and I also practiced a bit with Context API but rn I'm trying to create a context for theming.
I know there's already a library for that but I want to practice, and btw even if there's a library for it I would anyways need help to create a context for my login stuff (user basic information and things like that).
I know there's already a library for that but I want to practice, and btw even if there's a library for it I would anyways need help to create a context for my login stuff (user basic information and things like that).
9 Replies
Nile CrocodileOP
My Context code is this one:
My layout:
"use client"
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
interface ThemeContextType {
theme: string;
setTheme: (theme: string) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme debe usarse dentro de un ThemeProvider');
}
return context;
};
// const useTheme = () => {
// const context = useContext(ThemeContext);
// return context;
// }
export default useTheme
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [theme, setTheme] = useState('light'); // Estado inicial
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
setTheme(storedTheme);
}
}, []);
useEffect(() => {
localStorage.setItem("theme", theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};My layout:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { ThemeProvider } from './context/Theme'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'My title',
description: 'My description',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<ThemeProvider>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</ThemeProvider>
)
}My page:
"use client"
import Image from 'next/image'
import useTheme from './context/Theme'
// import { useTheme } from './context/Theme'
export default function Home() {
const { theme, setTheme } = useTheme();
// const theming = useTheme();
return (
<main>
<button onClick={() => theme === 'dark' || theme === '' ? setTheme('light') : setTheme('dark') }>Click to switch theme</button>
</main>
)
}Bracco Italiano
Hello @Nile Crocodile, you gave us the context and the code, but what's your question? Do you have any error?
@Bracco Italiano Hello <@1160883764101132288>, you gave us the context and the code, but what's your question? Do you have any error?
Nile CrocodileOP
en gros mon soucis c'était qu'en créant mon contexte de thème je puisse l'utiliser dans des composants ou pages SSR (donc sans le "use client"), mais j'étais un peu con et puis c'est bon j'ai pu régler ce soucis.
Maintenant autre problème c'est que quand mon dark mode est activé, la page met entre 1 et 2 secondes à établir le fond en dark
Maintenant autre problème c'est que quand mon dark mode est activé, la page met entre 1 et 2 secondes à établir le fond en dark
(j'sais pas si c'est obligatoire de parler anglais pour aider les autres alors désolé 😬)
"use client"
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
interface ThemeContextType {
theme: string;
setTheme: (theme: string) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme has to be wrapped into the ThemeProvider');
}
return context;
};
export default useTheme
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const getInitialTheme = () => {
if (typeof window !== 'undefined') {
const storedTheme = localStorage.getItem('theme');
return storedTheme || 'light';
}
return 'light';
};
const [theme, setTheme] = useState(getInitialTheme);
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
setTheme(storedTheme);
updateHtmlClass(storedTheme);
}
}, []);
const updateHtmlClass = (currentTheme: string) => {
if (currentTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
useEffect(() => {
localStorage.setItem("theme", theme);
updateHtmlClass(theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};là tout fonctionne correctement:
1) le localstorage s'actualise
2) la classe dans le <html> est aussi actualisé
3) les styles sont appliqués dès le rechargement de la page
Problème: ça met du temps avant de capter que l'utilisateur est en dark mode via le localstorage au moment du rechargement
1) le localstorage s'actualise
2) la classe dans le <html> est aussi actualisé
3) les styles sont appliqués dès le rechargement de la page
Problème: ça met du temps avant de capter que l'utilisateur est en dark mode via le localstorage au moment du rechargement
@Nile Crocodile en gros mon soucis c'était qu'en créant mon contexte de thème je puisse l'utiliser dans des composants ou pages SSR (donc sans le "use client"), mais j'étais un peu con et puis c'est bon j'ai pu régler ce soucis.
Maintenant autre problème c'est que quand mon dark mode est activé, la page met entre 1 et 2 secondes à établir le fond en dark
Bracco Italiano
Tu ne pourras pas utiliser ton hook useTheme dans les composants serveurs, car tu as un state dedans. Seuls les composants clients peuvent utiliser les states.
Ton soucis de délai de passage sur le bon theme, ça s'appelle le "flash on load"/"flicker problem", ça fait partie de certains edge cases à gérer pour faire les themes. C'est pour ça que habituellement on utilise des libs comme [next-themes](https://github.com/pacocoursey/next-themes) qui le gèrent pour nous correctement.
Pour mieux t'expliquer le soucis, c'est que les useEffect sont appelés côté client bien après que la page générée côté serveur te soit reçue côté client.
Le serveur génère ta page HTML -> Le serveur t'envoie la page -> Ton navigateur reçoit la page et te l'affiche (tu vois donc sûrement le thème par défaut dans ton cas) -> L'hydration se fait, tes composants client s'activent et les useEffect sont appelés (ta page prend à ce moment là enfin le bon thème)
Pour corriger ce soucis de "flash on load", y'a plusieurs méthodes, bonnes et mauvaises. Les cookies, un script JS dans les headers qui se lance avant l'hydration, ... Je ne m'y connais pas assez, mais tu peux regarder comment [next-themes fait dans son code source](https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx).
Ton soucis de délai de passage sur le bon theme, ça s'appelle le "flash on load"/"flicker problem", ça fait partie de certains edge cases à gérer pour faire les themes. C'est pour ça que habituellement on utilise des libs comme [next-themes](https://github.com/pacocoursey/next-themes) qui le gèrent pour nous correctement.
Pour mieux t'expliquer le soucis, c'est que les useEffect sont appelés côté client bien après que la page générée côté serveur te soit reçue côté client.
Le serveur génère ta page HTML -> Le serveur t'envoie la page -> Ton navigateur reçoit la page et te l'affiche (tu vois donc sûrement le thème par défaut dans ton cas) -> L'hydration se fait, tes composants client s'activent et les useEffect sont appelés (ta page prend à ce moment là enfin le bon thème)
Pour corriger ce soucis de "flash on load", y'a plusieurs méthodes, bonnes et mauvaises. Les cookies, un script JS dans les headers qui se lance avant l'hydration, ... Je ne m'y connais pas assez, mais tu peux regarder comment [next-themes fait dans son code source](https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx).
Nile CrocodileOP
nice to know tout ça, merci pour les explications je jette un coup d'oeil le code course de next-themes ðŸ˜