Skip to content

Commit a5c4a7d

Browse files
author
ankitrewar11
committed
fix: resolve dark mode flicker issue
Signed-off-by: ankitrewar11 <ankitrewar11@gmail.com>
1 parent 765b034 commit a5c4a7d

File tree

3 files changed

+76
-76
lines changed

3 files changed

+76
-76
lines changed

onRenderBody.js

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,70 @@
11
import React from "react";
2-
import { DarkThemeKey, ThemeSetting } from "./src/theme/app/ThemeManager.js";
2+
import { DarkThemeKey, ThemeSetting } from "./src/theme/app/ThemeManager";
33
import lighttheme, { darktheme } from "./src/theme/app/themeStyles";
44

5-
const themes = { light: lighttheme, dark: darktheme };
5+
const themes = {
6+
light: lighttheme,
7+
dark: darktheme,
8+
};
9+
10+
const MagicScriptTag = ({ theme }) => {
11+
const themeJSON = JSON.stringify(theme);
612

7-
const MagicScriptTag = (props) => {
813
const codeToRunOnClient = `
9-
(function() {
10-
// 1. Keeps SYSTEM as the priority preference
11-
const themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';
12-
13-
// 2. We change the check to look for LIGHT mode explicitly
14-
const systemLightModeSetting = () => window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;
15-
16-
const isLightModeActive = () => {
17-
return !!systemLightModeSetting()?.matches;
18-
};
19-
20-
let colorMode;
21-
switch (themeFromLocalStorage) {
22-
case '${ThemeSetting.SYSTEM}':
23-
// LOGIC CHANGE: If Light is active -> Light. Otherwise (Dark, No Preference, or Error) -> Dark.
24-
colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}'
25-
break
26-
case '${ThemeSetting.DARK}':
27-
case '${ThemeSetting.LIGHT}':
28-
colorMode = themeFromLocalStorage
29-
break
30-
default:
31-
// 3. Fallback to DARK in case of error
32-
colorMode = '${ThemeSetting.DARK}'
33-
}
34-
35-
const root = document.documentElement;
36-
const iterate = (obj) => {
37-
if (!obj) return;
38-
Object.keys(obj).forEach(key => {
39-
if (typeof obj[key] === 'object') {
40-
iterate(obj[key])
41-
} else {
42-
root.style.setProperty("--" + key, obj[key])
43-
}
44-
})
45-
}
46-
const parsedTheme = JSON.parse('${JSON.stringify(props.theme)}')
47-
const theme = parsedTheme[colorMode]
48-
iterate(theme)
49-
root.style.setProperty('--initial-color-mode', colorMode);
50-
})()
51-
`;
14+
(function() {
15+
try {
16+
var themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';
17+
var systemLightModeSetting = function() {
18+
return window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;
19+
};
20+
var isLightModeActive = function() {
21+
var mql = systemLightModeSetting();
22+
return mql ? mql.matches : false;
23+
};
24+
25+
var colorMode;
26+
switch (themeFromLocalStorage) {
27+
case '${ThemeSetting.SYSTEM}':
28+
colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}';
29+
break;
30+
case '${ThemeSetting.DARK}':
31+
case '${ThemeSetting.LIGHT}':
32+
colorMode = themeFromLocalStorage;
33+
break;
34+
default:
35+
colorMode = '${ThemeSetting.DARK}';
36+
}
37+
38+
var root = document.documentElement;
39+
var parsedTheme = ${themeJSON};
40+
var selectedTheme = parsedTheme[colorMode];
41+
42+
var iterate = function(obj) {
43+
if (!obj) return;
44+
Object.keys(obj).forEach(function(key) {
45+
if (typeof obj[key] === 'object' && obj[key] !== null) {
46+
iterate(obj[key]);
47+
} else {
48+
root.style.setProperty('--' + key, obj[key]);
49+
}
50+
});
51+
};
52+
53+
iterate(selectedTheme);
54+
root.style.setProperty('--initial-color-mode', colorMode);
55+
root.setAttribute('data-theme', colorMode);
56+
57+
window.__theme = colorMode;
58+
59+
} catch (e) {}
60+
})();
61+
`;
62+
5263
return <script dangerouslySetInnerHTML={{ __html: codeToRunOnClient }} />;
5364
};
5465

55-
export const onRenderBody = ( { setPreBodyComponents }) => {
56-
setPreBodyComponents(<MagicScriptTag key="theme-injection" theme={themes} />);
66+
export const onRenderBody = ({ setHeadComponents }) => {
67+
setHeadComponents([
68+
<MagicScriptTag key="theme-initializer" theme={themes} />,
69+
]);
5770
};

src/theme/app/StyledThemeProvider.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
//uses isDark state to choose styled-component theme (in themeStyles.js)
2-
//and use ThemeProvider to allow all styled components access to values via props.theme
3-
41
import React, { useContext } from "react";
52
import { ThemeProvider } from "styled-components";
63
import { ThemeManagerContext } from "./ThemeManager";
74

8-
// Safe check for browser environment
95
const isBrowser = typeof window !== "undefined";
106

117
export const StyledThemeProvider = (props) => {
128
const { children, darkTheme, lightTheme } = props;
139
const { isDark, didLoad } = useContext(ThemeManagerContext);
1410

15-
// For SSR, we need to provide a consistent theme initially
16-
// This ensures the server and client render the same thing initially
1711
const currentTheme = isDark ? darkTheme : lightTheme;
1812
const theme = {
19-
...(didLoad || !isBrowser ? currentTheme : transformTheme(currentTheme)),
13+
...(didLoad ? currentTheme : transformTheme(currentTheme)),
2014
};
2115

2216
return (
@@ -39,5 +33,3 @@ const transformTheme = (theme) => {
3933

4034
return newTheme;
4135
};
42-
43-

src/theme/app/ThemeManager.js

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//majority of code from https://www.joshwcomeau.com/react/dark-mode/ and https://github.com/gperl27/gatsby-styled-components-dark-mode
2-
3-
//context provider for app to make accessible theme setting, toggle function, etc.
4-
51
import React, { createContext, useState, useEffect, useCallback } from "react";
62

73
export const ThemeSetting = {
@@ -22,11 +18,11 @@ const defaultState = {
2218

2319
export const ThemeManagerContext = createContext(defaultState);
2420

25-
// Safe check for browser environment
2621
const isBrowser = typeof window !== "undefined";
2722

2823
const systemDarkModeSetting = () =>
2924
isBrowser && window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
25+
3026
const isDarkModeActive = () => {
3127
return !!systemDarkModeSetting()?.matches;
3228
};
@@ -36,32 +32,39 @@ const applyThemeToDOM = (theme) => {
3632
const root = window.document.documentElement;
3733
root.style.setProperty("--initial-color-mode", theme);
3834
root.setAttribute("data-theme", theme);
35+
window.__theme = theme;
3936
};
4037

4138
export const ThemeManagerProvider = (props) => {
4239
const [themeSetting, setThemeSetting] = useState(ThemeSetting.SYSTEM);
4340
const [didLoad, setDidLoad] = useState(false);
44-
const [isDark, setIsDark] = useState(false);
41+
42+
const [isDark, setIsDark] = useState(() => {
43+
if (isBrowser) {
44+
if (window.__theme === ThemeSetting.DARK) return true;
45+
if (window.__theme === ThemeSetting.LIGHT) return false;
46+
}
47+
return false;
48+
});
4549

4650
useEffect(() => {
4751
if (!isBrowser) return;
4852

4953
const root = window.document.documentElement;
50-
const initialColorValue = root.style.getPropertyValue("--initial-color-mode");
54+
const initialColorValue = (root.style.getPropertyValue("--initial-color-mode") || "").trim();
55+
const actualTheme = window.__theme || initialColorValue || ThemeSetting.LIGHT;
5156

52-
// Get stored theme from localStorage
5357
const storedTheme = localStorage.getItem(DarkThemeKey);
5458

5559
if (storedTheme && storedTheme !== ThemeSetting.SYSTEM) {
5660
const isDarkTheme = storedTheme === ThemeSetting.DARK;
5761
setIsDark(isDarkTheme);
5862
setThemeSetting(storedTheme);
5963
applyThemeToDOM(storedTheme);
60-
} else if (initialColorValue) {
61-
setIsDark(initialColorValue === ThemeSetting.DARK);
64+
} else if (actualTheme) {
65+
setIsDark(actualTheme === ThemeSetting.DARK);
6266
setThemeSetting(ThemeSetting.SYSTEM);
6367
} else {
64-
// Fallback to system preference
6568
const systemIsDark = isDarkModeActive();
6669
setIsDark(systemIsDark);
6770
const theme = systemIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;
@@ -71,7 +74,6 @@ export const ThemeManagerProvider = (props) => {
7174
setDidLoad(true);
7275
}, []);
7376

74-
// Listen to system color scheme changes only when on SYSTEM mode
7577
useEffect(() => {
7678
if (!isBrowser || themeSetting !== ThemeSetting.SYSTEM) return;
7779

@@ -93,14 +95,11 @@ export const ThemeManagerProvider = (props) => {
9395
const newIsDark = !isDark;
9496
const newTheme = newIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;
9597

96-
// Update state
9798
setIsDark(newIsDark);
9899
setThemeSetting(newTheme);
99100

100-
// Apply to DOM immediately
101101
applyThemeToDOM(newTheme);
102102

103-
// Persist to localStorage
104103
localStorage.setItem(DarkThemeKey, newTheme);
105104
}, [isDark]);
106105

@@ -129,14 +128,10 @@ export const ThemeManagerProvider = (props) => {
129128
return;
130129
}
131130

132-
// Update state
133131
setIsDark(newIsDark);
134132
setThemeSetting(setting);
135133

136-
// Apply to DOM immediately
137134
applyThemeToDOM(themeToApply);
138-
139-
// Persist to localStorage
140135
localStorage.setItem(DarkThemeKey, setting);
141136
},
142137
[isDark]

0 commit comments

Comments
 (0)