Open quantizor opened 1 year ago
hi, i have the problem that my styles only load when i click the DarkmodeToggle button. On first load there is no style and on refresh of the page also not all styles load only when I use the button.
my registry.tsx:
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return styles
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}
my ConfigContext.tsx:
* This context holds user configuration stuff, like:
* - Dark or light mode
* - Sound enabled or disabled
*/
import { ReactNode, createContext, useCallback, useEffect, useMemo, useState } from 'react';
import {
DARK_COLORS,
LIGHT_COLORS,
PREFERS_DARK_KEY,
PREFERS_DARK_CSS_PROP,
ColorType,
} from '@constants';
import usePersistedState from '@hooks/usePersistedState';
export const ConfigContext = createContext<{
colorMode?: string;
setColorMode?: (value: "light" | "dark") => void;
soundEnabled?: boolean;
setSoundEnabled?: (newValue: boolean) => void;
allowColorTransitions?: boolean;
disableTabInCodeSnippets?: boolean;
setDisableTabInCodeSnippets?: (value: boolean) => void;
} | null>(null);
const SOUND_ENABLED_KEY = 'sound-enabled';
export const ConfigProvider = ({ children }: {children: ReactNode}) => {
let initialColorValue = 'light';
let initialSoundEnabled = true;
let initialAllowColorTransitions = false;
const [colorMode, rawSetColorMode] = useState(
initialColorValue
);
const [soundEnabled, rawSetSoundEnabled] = useState(
initialSoundEnabled
);
const [
disableTabInCodeSnippets,
setDisableTabInCodeSnippets,
] = usePersistedState(true, 'tab-in-code-snippets');
const [
allowColorTransitions,
setAllowColorTransitions,
] = useState(initialAllowColorTransitions);
useEffect(() => {
// Immediately after mount, trigger a re-render IF the values
// in localStorage don't match the values in the statically-
// generated HTML
let root = window.document.documentElement;
const localColorValue =
root.style.getPropertyValue(PREFERS_DARK_CSS_PROP) === 'true'
? 'dark'
: 'light';
const localSoundEnabled =
window.localStorage?.getItem(SOUND_ENABLED_KEY) === 'false'
? false
: true;
if (localColorValue !== initialColorValue) {
rawSetColorMode(localColorValue);
}
if (localSoundEnabled !== initialSoundEnabled) {
rawSetSoundEnabled(localSoundEnabled);
}
}, []);
const setColorMode = useCallback(
(value: 'light' | 'dark') => {
if (!allowColorTransitions) {
setAllowColorTransitions(true);
}
let root = window.document.documentElement;
root.setAttribute('data-color-mode', value);
const prefersDark = value === 'dark';
root.style.setProperty(PREFERS_DARK_CSS_PROP, String(prefersDark));
const newColors = prefersDark ? DARK_COLORS : LIGHT_COLORS;
root.style.setProperty('--color-text', newColors.text!);
root.style.setProperty(
'--color-background',
newColors.background!
);
root.style.setProperty(
'--color-blurred-background',
newColors.blurredBackground!
);
root.style.setProperty('--color-primary', newColors.primary!);
root.style.setProperty(
'--color-secondary',
newColors.secondary!
);
root.style.setProperty('--color-tertiary', newColors.tertiary!);
root.style.setProperty(
'--color-decorative',
newColors.decorative!
);
root.style.setProperty('--color-muted', newColors.muted!);
root.style.setProperty(
'--color-muted-background',
newColors.mutedBackground!
);
root.style.setProperty('--color-info', newColors.info || '');
root.style.setProperty('--color-success', newColors.success!);
root.style.setProperty(
'--color-success-background',
newColors.successBackground!
);
root.style.setProperty('--color-error', newColors.error!);
root.style.setProperty(
'--color-error-background',
newColors.errorBackground!
);
root.style.setProperty('--color-alert', newColors.alert!);
root.style.setProperty(
'--color-alert-background',
newColors.alertBackground!
);
root.style.setProperty('--color-venn-0', newColors.venn![0]);
root.style.setProperty('--color-venn-1', newColors.venn![1]);
root.style.setProperty('--color-gray-100', newColors.gray![100]);
root.style.setProperty('--color-gray-200', newColors.gray![200]);
root.style.setProperty('--color-gray-300', newColors.gray![300]);
root.style.setProperty('--color-gray-400', newColors.gray![400]);
root.style.setProperty('--color-gray-500', newColors.gray![500]);
root.style.setProperty('--color-gray-600', newColors.gray![600]);
root.style.setProperty('--color-gray-700', newColors.gray![700]);
root.style.setProperty('--color-gray-900', newColors.gray![900]);
root.style.setProperty(
'--color-gray-1000',
newColors.gray![1000]
);
root.style.setProperty(
'--color-subtle-background',
newColors.subtleBackground || ''
);
root.style.setProperty(
'--color-subtle-floating',
newColors.subtleFloating || ''
);
root.style.setProperty(
'--color-homepage-light',
newColors.homepageLight || ''
);
root.style.setProperty(
'--color-homepage-dark',
newColors.homepageDark || ''
);
root.style.setProperty(
'--color-homepage-bg',
newColors.homepageBg || ''
);
root.style.setProperty('--syntax-bg', newColors.syntax!.bg);
root.style.setProperty(
'--syntax-highlight',
newColors.syntax!.highlight
);
root.style.setProperty('--syntax-txt', newColors.syntax!.txt);
root.style.setProperty(
'--syntax-comment',
newColors.syntax!.comment
);
root.style.setProperty('--syntax-prop', newColors.syntax!.prop);
root.style.setProperty('--syntax-bool', newColors.syntax!.bool);
root.style.setProperty('--syntax-val', newColors.syntax!.val);
root.style.setProperty('--syntax-str', newColors.syntax!.str);
root.style.setProperty('--syntax-name', newColors.syntax!.name);
root.style.setProperty('--syntax-del', newColors.syntax!.del);
root.style.setProperty(
'--syntax-regex',
newColors.syntax!.regex
);
root.style.setProperty('--syntax-fn', newColors.syntax!.fn);
rawSetColorMode(value);
localStorage.setItem(PREFERS_DARK_KEY, String(prefersDark));
},
[allowColorTransitions]
);
// Listen for changes in the media query
useEffect(() => {
const QUERY = '(prefers-color-scheme: dark)';
const mediaQueryList = window.matchMedia(QUERY);
const listener = (event: MediaQueryListEvent) => {
const isDark = event.matches;
setColorMode(isDark ? 'dark' : 'light');
};
if (mediaQueryList.addEventListener) {
mediaQueryList.addEventListener('change', listener);
} else {
mediaQueryList.addListener(listener);
}
return () => {
if (mediaQueryList.removeEventListener) {
mediaQueryList.removeEventListener('change', listener);
} else {
mediaQueryList.removeListener(listener);
}
};
}, []);
const value = useMemo(() => {
const setSoundEnabled = (newValue: boolean) => {
window.localStorage?.setItem(SOUND_ENABLED_KEY, String(newValue));
rawSetSoundEnabled(newValue);
};
return {
colorMode,
setColorMode,
soundEnabled,
setSoundEnabled,
allowColorTransitions,
disableTabInCodeSnippets,
setDisableTabInCodeSnippets,
};
}, [
colorMode,
setColorMode,
rawSetColorMode,
soundEnabled,
rawSetSoundEnabled,
allowColorTransitions,
disableTabInCodeSnippets,
setDisableTabInCodeSnippets,
]);
return (
<ConfigContext.Provider value={value}>
{children}
</ConfigContext.Provider>
);
};
my layout.tsx:
import GlobalStyle from '@components/GlobalStyle'
import { Inter } from 'next/font/google'
import { ThemeProvider } from 'styled-components'
import { THEME } from '@constants'
import StyledComponentsRegistry from '@lib/registry'
import { ConfigProvider } from '@components/ConfigContext'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<StyledComponentsRegistry>
<ConfigProvider>
<ThemeProvider theme={THEME}>
<GlobalStyle allowColorTransitions={true} />
<body className={inter.className}>{children}</body>
</ThemeProvider>
</ConfigProvider>
</StyledComponentsRegistry>
</html>
)
}
my GlobalStyle.tsx:
import React from 'react'
import { createGlobalStyle } from 'styled-components'
import '@nyxb/reset.css'
import { COLOR_SWAP_TRANSITION_DURATION } from '@constants';
export interface GlobalStylesProps {
allowColorTransitions?: boolean;
}
const GlobalStyles = createGlobalStyle<GlobalStylesProps>`
/* Global styles */
body {
color: var(--color-text);
background: var(--color-background);
transition: ${(p) => {
if (!p.allowColorTransitions) {
return null;
}
return `color ${COLOR_SWAP_TRANSITION_DURATION}ms, background ${COLOR_SWAP_TRANSITION_DURATION}ms`;
}};
}
a:focus {
outline: 5px auto var(--color-primary);
}
body, input, button, select, option {
font-family: var(--font-family);
font-weight: var(--font-weight-light);
}
h1, h2, h3, h4, h5, h6, strong {
font-weight: var(--font-weight-bold);
}
code {
font-size: 0.95em;
}
::selection {
background-color: var(--selection-background-color, var(--color-primary));
color: var(--selection-text-color, white);
}
/* Scrollbar and selection styles */
::selection {
background-color: var(--selection-background-color, var(--color-primary));
color: var(--selection-text-color, white);
background-image: none !important;
-webkit-text-fill-color: var(--selection-text-color) !important;
-moz-text-fill-color: var(--selection-text-color) !important;
background-image: none !important;
background-clip: revert !important;
-webkit-background-clip: revert !important;
text-shadow: none !important;
}
@media (orientation: landscape) {
::-webkit-scrollbar {
width: var(--scrollbar-width, 12px);
height: var(--scrollbar-height, 12px);
background-color: var(--scrollbar-background-color, var(--color-gray-100));
}
::-webkit-scrollbar-track {
border-radius: 3px;
background-color: var(--scrollbar-background-color, transparent);
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: var(--scrollbar-color, var(--color-gray-700));
border: 2px solid var(--scrollbar-background-color, var(--color-gray-100));
}
}
/* CSS Variables */
:root {
--font-weight-bold: 600;
--font-weight-medium: 500;
--font-weight-light: 400;
--font-family: 'Wotfard', 'Wotfard-fallback', sans-serif;
--font-family-mono: 'League Mono', 'Fira Mono', monospace;
--font-family-spicy: 'Sriracha', 'Wotfard', Futura, -apple-system, sans-serif;
/* HACK:
Reach UI tests for loaded styles, but I'm providing my own.
This is to avoid a noisy warning in dev.
*/
--reach-dialog: 1;
}
.video-js .vjs-big-play-button {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
margin: auto !important;
border: 1px solid rgba(255, 255, 255, 0.25) !important;
background-color: rgba(0, 0, 0, 0.4) !important;
}
.video-js .vjs-play-progress:before {
top: -0.6em !important;
}
.vjs-slider-horizontal .vjs-volume-level:before {
top: -0.6em !important;
}
`;
const GlobalStylesWrapper = (props: any) => {
return <GlobalStyles {...props} />;
};
export default React.memo(GlobalStylesWrapper);
when loading for the first time or after a reload it looks like this:
if i click the button now:
if I click it again for light mode:
I've been trying for weeks now, haven't found anything in the nice and nextjs forum didn't give me any answers and no one replied to my post. I just do not understand what I am doing wrong. How do I make one of the styles exist on first load?
Hi @quantizor, it looks like this is still not implemented on the site (this is all we have on Next.js https://styled-components.com/docs/advanced#next.js). Is there a new fix, or do I stick to the steps above for a PR?
PR welcome!
If anyone needs a step by step on how to make styled-components work with Next.js 13 (app router) without any "delay bugs" using client-side rendering, here it is:
Step 1: add the following to your next.config.js file
Step 2: create the registry.tsx file with the following code:
Step 3: add the 'use client' directive to your layout.tsx file and wrap all the children components on your layout with the StyledComponentsRegistry component.
I've made a tutorial if anyone needs further help: https://www.youtube.com/watch?v=3tgrPm2aKog
Originally posted by @lucasmelz in https://github.com/styled-components/styled-components/issues/3856#issuecomment-1597789823