[WEB-5772] fix: theme switch flicker (#8428)

This commit is contained in:
Anmol Singh Bhatia 2025-12-23 15:01:01 +05:30 committed by GitHub
parent 70eea50db5
commit 5590bf7198
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { useTheme } from "next-themes";
@ -28,6 +28,13 @@ function StoreWrapper(props: TStoreWrapper) {
const { data: userProfile } = useUserProfile();
const { changeLanguage } = useTranslation();
// Track if we've initialized theme from server (one-time only)
const hasInitializedThemeRef = useRef(false);
// Track current user to reset on logout/login
const currentUserIdRef = useRef<string | undefined>(undefined);
// Track previous theme to detect transitions from custom theme
const previousThemeRef = useRef<string | undefined>(undefined);
/**
* Sidebar collapsed fetching from local storage
*/
@ -38,25 +45,61 @@ function StoreWrapper(props: TStoreWrapper) {
}, [sidebarCollapsed, setTheme, toggleSidebar]);
/**
* Setting up the theme of the user by fetching it from profile
* Effect 1: Initial theme sync from server (one-time only)
*
* This effect runs ONCE per user session to load theme from server.
* After initial load, all theme changes are localStorage-driven (next-themes).
* This prevents a feedback loop where server updates trigger UI updates in a cycle.
*/
useEffect(() => {
const userId = userProfile?.id;
// Reset initialization flag when user changes (logout/login)
// This handles both logout (userId becomes undefined) and login (userId changes)
if (userId !== currentUserIdRef.current) {
hasInitializedThemeRef.current = false;
previousThemeRef.current = undefined;
currentUserIdRef.current = userId;
}
// Only initialize theme from server on FIRST load for this user
if (!userProfile?.theme?.theme || hasInitializedThemeRef.current) {
return; // Skip if already initialized or no profile data
}
// Apply theme from server profile (one-time only)
setTheme(userProfile?.theme?.theme || "system");
// Mark as initialized - prevents future syncs from server
hasInitializedThemeRef.current = true;
}, [userProfile?.theme?.theme, setTheme]);
/**
* Effect 2: Custom theme CSS application (runs on every change)
*
* This effect applies or clears custom theme CSS variables whenever
* the theme changes. It runs independently of the initial sync effect.
*/
useEffect(() => {
if (!userProfile?.theme?.theme) return;
const currentTheme = userProfile?.theme?.theme || "system";
const theme = userProfile?.theme;
if (currentTheme) {
setTheme(currentTheme);
if (currentTheme === "custom") {
// New 2-color palette system
if (theme.primary && theme.background && theme.darkPalette !== undefined) {
applyCustomTheme(theme.primary, theme.background, theme.darkPalette ? "dark" : "light");
}
} else {
clearCustomTheme();
}
const currentTheme = userProfile?.theme?.theme;
const previousTheme = previousThemeRef.current;
const themeData = userProfile?.theme;
// Apply custom theme if current theme is custom
if (currentTheme === "custom" && themeData.primary && themeData.background && themeData.darkPalette !== undefined) {
applyCustomTheme(themeData.primary, themeData.background, themeData.darkPalette ? "dark" : "light");
}
}, [userProfile?.theme, setTheme]);
// Clear custom theme CSS when switching away from custom
else if (previousTheme === "custom" && currentTheme !== "custom") {
clearCustomTheme();
// No reload needed - let CSS cascade handle it naturally
}
// Update previous theme for next comparison
previousThemeRef.current = currentTheme;
}, [userProfile?.theme]);
useEffect(() => {
if (!userProfile?.language) return;