Skip to content

Commit bfb1782

Browse files
committed
Safest Next.js setup resilient to concurent SSR rendering
1 parent 636c76f commit bfb1782

File tree

6 files changed

+269
-252
lines changed

6 files changed

+269
-252
lines changed

src/lib/darkMode.ts

Lines changed: 74 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import {
2-
createStatefulObservable,
3-
useRerenderOnChange,
4-
statefulObservableBidirectionalMap
5-
} from "./tools/StatefulObservable";
1+
import { createStatefulObservable, useRerenderOnChange } from "./tools/StatefulObservable";
2+
import { createContext, useContext } from "react";
63
import { useConstCallback } from "./tools/powerhooks/useConstCallback";
74
import { assert } from "tsafe/assert";
85
import { isBrowser } from "./tools/isBrowser";
@@ -12,65 +9,82 @@ export type ColorScheme = "light" | "dark";
129
export const data_fr_theme = "data-fr-theme";
1310
export const data_fr_scheme = "data-fr-scheme";
1411

15-
export const $colorScheme = createStatefulObservable<ColorScheme>(() => "light");
12+
//export const $colorScheme = createStatefulObservable<ColorScheme>(() => "light");
13+
export const $isDark = createStatefulObservable(() => false);
1614

17-
type UseColorScheme = () => {
18-
colorScheme: ColorScheme;
19-
setColorScheme: (colorSchemeOrSystem: ColorScheme | "system") => void;
15+
type UseIsDark = () => {
16+
isDark: boolean;
17+
setIsDark: (isDark: boolean | "system") => void;
2018
};
2119

22-
const useColorSchemeClientSide: UseColorScheme = () => {
23-
useRerenderOnChange($colorScheme);
20+
const useIsDarkClientSide: UseIsDark = () => {
21+
useRerenderOnChange($isDark);
2422

25-
const setColorScheme = useConstCallback((colorSchemeOrSystem: ColorScheme | "system") =>
26-
document.documentElement.setAttribute(data_fr_scheme, colorSchemeOrSystem)
23+
const setIsDark = useConstCallback((isDark: boolean | "system") =>
24+
document.documentElement.setAttribute(
25+
data_fr_scheme,
26+
((): ColorScheme | "system" => {
27+
switch (isDark) {
28+
case "system":
29+
return "system";
30+
case true:
31+
return "dark";
32+
case false:
33+
return "light";
34+
}
35+
})()
36+
)
2737
);
2838

29-
return { "colorScheme": $colorScheme.current, setColorScheme };
39+
return { "isDark": $isDark.current, setIsDark };
3040
};
3141

32-
const useColorSchemeServerSide: UseColorScheme = () => {
33-
const setColorScheme = useConstCallback(() => {
42+
export const isDarkContext = createContext<boolean | undefined>(undefined);
43+
44+
const useIsDarkServerSide: UseIsDark = () => {
45+
const setIsDark = useConstCallback(() => {
3446
/* nothing */
3547
});
3648

49+
const isDark = useContext(isDarkContext);
50+
51+
assert(isDark !== undefined, "color scheme context should be provided");
52+
3753
return {
38-
"colorScheme": $colorScheme.current,
39-
setColorScheme
54+
isDark,
55+
setIsDark
4056
};
4157
};
4258

43-
export const useColorScheme = isBrowser ? useColorSchemeClientSide : useColorSchemeServerSide;
44-
45-
function getCurrentColorSchemeFromHtmlAttribute(): ColorScheme {
46-
if (!isBrowser) {
47-
return "light";
48-
}
59+
export const useIsDark = isBrowser ? useIsDarkClientSide : useIsDarkServerSide;
4960

61+
function getCurrentIsDarkFromHtmlAttribute(): boolean {
5062
const colorSchemeReadFromDom = document.documentElement.getAttribute(data_fr_theme);
5163

5264
switch (colorSchemeReadFromDom) {
5365
case null:
54-
return "light";
5566
case "light":
67+
return false;
5668
case "dark":
57-
return colorSchemeReadFromDom;
69+
return true;
5870
}
5971

6072
assert(false);
6173
}
6274

6375
export function startObservingColorSchemeHtmlAttribute() {
64-
$colorScheme.current = getCurrentColorSchemeFromHtmlAttribute();
76+
$isDark.current = getCurrentIsDarkFromHtmlAttribute();
6577

66-
new MutationObserver(
67-
() => ($colorScheme.current = getCurrentColorSchemeFromHtmlAttribute())
68-
).observe(document.documentElement, {
69-
"attributes": true,
70-
"attributeFilter": [data_fr_theme]
71-
});
78+
new MutationObserver(() => ($isDark.current = getCurrentIsDarkFromHtmlAttribute())).observe(
79+
document.documentElement,
80+
{
81+
"attributes": true,
82+
"attributeFilter": [data_fr_theme]
83+
}
84+
);
7285

7386
{
87+
/*
7488
const setColorSchemeCookie = (colorScheme: ColorScheme) => {
7589
let newCookie = `${data_fr_theme}=${colorScheme};path=/;max-age=31536000`;
7690
@@ -87,17 +101,39 @@ export function startObservingColorSchemeHtmlAttribute() {
87101
88102
document.cookie = newCookie;
89103
};
104+
*/
90105

91-
setColorSchemeCookie($colorScheme.current);
106+
const setColorSchemeCookie = (isDark: boolean) => {
107+
const colorScheme: ColorScheme = isDark ? "dark" : "light";
92108

93-
$colorScheme.subscribe(setColorSchemeCookie);
109+
let newCookie = `${data_fr_theme}=${colorScheme};path=/;max-age=31536000`;
110+
111+
set_domain: {
112+
const { hostname } = window.location;
113+
114+
//We do not set the domain if we are on localhost or an ip
115+
if (/(^localhost$)|(^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$)/.test(hostname)) {
116+
break set_domain;
117+
}
118+
119+
newCookie += `;domain=${hostname}`;
120+
}
121+
122+
document.cookie = newCookie;
123+
};
124+
125+
setColorSchemeCookie($isDark.current);
126+
127+
$isDark.subscribe(setColorSchemeCookie);
94128
}
95129

96130
//TODO: <meta name="theme-color" content="#000091"><!-- Défini la couleur de thème du navigateur (Safari/Android) -->
97131

98132
//TODO: Remove once https://github.com/GouvernementFR/dsfr/issues/407 is dealt with
99133
{
100-
const setRootColorScheme = (colorScheme: ColorScheme) => {
134+
const setRootColorScheme = (isDark: boolean) => {
135+
const colorScheme: ColorScheme = isDark ? "dark" : "light";
136+
101137
const id = "root-color-scheme";
102138

103139
remove_existing_element: {
@@ -119,45 +155,8 @@ export function startObservingColorSchemeHtmlAttribute() {
119155
document.getElementsByTagName("head")[0].appendChild(element);
120156
};
121157

122-
setRootColorScheme($colorScheme.current);
158+
setRootColorScheme($isDark.current);
123159

124-
$colorScheme.subscribe(setRootColorScheme);
160+
$isDark.subscribe(setRootColorScheme);
125161
}
126162
}
127-
128-
//NOTE: Just because it's more convenient to have a boolean than "light" | "dark"
129-
130-
export const $isDark = statefulObservableBidirectionalMap({
131-
"statefulObservable": $colorScheme,
132-
"trInToOut": colorScheme => {
133-
switch (colorScheme) {
134-
case "light":
135-
return false;
136-
case "dark":
137-
return true;
138-
}
139-
},
140-
"trOutToIn": isDark => (isDark ? "dark" : "light")
141-
});
142-
143-
export function useIsDark() {
144-
const { colorScheme, setColorScheme } = useColorScheme();
145-
146-
const setIsDark = useConstCallback((isDark: boolean | "system") =>
147-
setColorScheme(typeof isDark !== "boolean" ? isDark : isDark ? "dark" : "light")
148-
);
149-
150-
const isDark = (() => {
151-
switch (colorScheme) {
152-
case "dark":
153-
return true;
154-
case "light":
155-
return false;
156-
}
157-
})();
158-
159-
return {
160-
isDark,
161-
setIsDark
162-
};
163-
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from "./StatefulObservable";
22
export * from "./hooks";
3-
export * from "./statefulObservableBidirectionalMap";

src/lib/tools/StatefulObservable/statefulObservableBidirectionalMap.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)