How do I resolve React flash error using dark mode? - reactjs

I am using a theme toggler function for dark mode. This function lives inside my src/components/Toggle/Toggle.jsx file, which renders a toggle button that changes the theme. When I reload my page, I get a white flash. I think this is happening because my scripts are not rendering before my HTML doc and it appears the solution is to add a script to my <head>....but I am unsure how to move my Toggle.jsx function out of that component into my <head>...any ideas?
Below is my dark mode function inside my Toggle.jsx...
import "./Toggle.css";
import Icon from "../Icon/Icon";
function Toggle() {
//optionally parse from localStorage
const [theme, setTheme] = React.useState(getInitialMode());
React.useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", JSON.stringify(theme));
}, [theme]);
function getInitialMode() {
const isReturningUser = "theme" in localStorage;
const savedMode = JSON.parse(localStorage.getItem("theme"));
const userPrefersDark = getPrefColorScheme();
// if mode was saved -> dark / light
if (isReturningUser) {
return savedMode;
}
// if preferred color scheme is dark -> dark
else if (userPrefersDark) {
return "dark";
} else {
// otherwise -> light
return "light";
}
}
function getPrefColorScheme() {
if (!window.matchMedia) return;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}
return (
<button
className="toggle-button"
onClick={() => {
setTheme(theme === "light" ? "dark" : "light");
}}
>
<Icon />
</button>
);
}
export default Toggle;

I guess easy option would be to set dark mode as default so whenever you refresh the page before the selected theme is loaded your dark theme will load and prevent your screen from flashing

Related

Warning: Prop `className` did not match. Switching from light to dark mode using cookies (Nextjs, Mui5)

When the browser loads if the cookie (darkMode) is set to OFF the light mode is on but when The cookie (darkMode) is set to ON which means the dark is on the browser does load the dark mode but only for the navbar and seems that it stops the rendering process and the warning happens I am using it with material ui switch.
import Cookies from 'js-cookie';
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
darkMode: Cookies.get('darkMode') === 'ON' ? true : false,
}
export const DarkModeSlice = createSlice({
name: 'darkMode',
initialState,
reducers: {
toggleDarkMode: (state) => {
let newDarkMode = !state.darkMode;
Cookies.set("darkMode", newDarkMode ? "ON" : "OFF");
state.darkMode = newDarkMode
}
},
})
export const { toggleDarkMode } = DarkModeSlice.actions
export default DarkModeSlice.reducer
This video explains how to switch from dark to light using next-themes form npm watch it if there are some errors and there will be, come back and continue reading my instructions
Dark Mode Persistence with Next.js is Harder than with plain React (MUI example)
INSTRUCTIONS
Please Do not forget to remove the <CssBaseline /> under the theme provider or PageProvider if you are gonna follow the video because next-themes that you will download form npm is gonna do every thing for you. also add key attribute if you are using switch component. Follow The code below.
import { useTheme } from "next-themes";
const { theme, resolvedTheme, setTheme } = useTheme();
const [isDark, setIsDark] = useState();
useEffect(() => {
resolvedTheme === "light" ? setIsDark(false) : setIsDark(true);
return () => {};
}, [resolvedTheme]);
<Switch
key={isDark}
checked={isDark}
onChange={() =>
setTheme(resolvedTheme === "light" ? "dark" : "light")
}
/>
Copy and Paste it, it is gonna work in the component you wanna use the switch in, If any one still could not solve it please feel free to contact me.

My image is not changing to another image in React on a darkmode click handler

Converting a vanilla javascript file to react and I'm trying to get an image to change when the dark mode button is clicked I'm having trouble getting it to work in React do I use onClick or onChange or maybe something else to accomplish this? Whenever I try to change it I just get an empty PNG instead of a changed image. To clarify, when I first load the page the first image shows up correctly. So I believe its just my changeImage function that's not working and I'm struggling to figure out why.
On another note, I don't believe my local storage command is working either because when I switch to dark mode and then reload the page, it's going back to light mode instead of staying in dark mode.
Thank you.
import React from "react";
import logo from "../images/portfolio logo option 2.png";
import darklogo from "../images/devjon darkmode.png";
function About() {
//changeImage function
function changeImage() {
let image = document.getElementById("myLogo");
if (image.src.match({ logo })) {
image.src = "/images/";
} else {
image.src = { darklogo };
}
}
function changeImage2() {
let image = document.getElementById("myLogo");
if (image.src.match({ darklogo })) {
image.src = "/images/";
} else {
image.src = { logo };
}
}
//dark mode button
function darkMode() {
let darkMode = localStorage.getItem("darkMode");
const darkModeToggle = document.querySelector("#dark-mode-toggle");
const enableDarkMode = () => {
changeImage();
// 1. Add the class to the body
document.body.classList.add("darkmode");
// 2. Update darkMode in localStorage
localStorage.setItem("darkMode", "enabled");
};
const disableDarkMode = () => {
changeImage2();
// 1. Remove the class from the body
document.body.classList.remove("darkmode");
// 2. Update darkMode in localStorage
localStorage.removeItem("darkMode");
};
// If the user already visited and enabled darkMode
// start things off with it on
if (darkMode === "enabled") {
enableDarkMode();
changeImage();
}
// When someone clicks the button
darkModeToggle.addEventListener("click", () => {
// get their darkMode setting
darkMode = localStorage.getItem("darkMode");
// if it not currently enabled, enable it
if (darkMode !== "enabled") {
enableDarkMode();
// if it has been enabled, turn it off
} else {
disableDarkMode();
}
});
}
//return the HTML
return (
<div>
{/* devJon logo */}
<div className="logo">
<img
src={logo}
id="myLogo"
onClick={changeImage}
alt="devJon logo"
className="dev-jon"
/>
</div>
{/* Darkmode Button */}
<div>
<button
id="dark-mode-toggle"
className="dark-mode-toggle"
onClick={darkMode}
>
<svg
width="20%"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 550 550"
>
<path
fill="currentColor"
d="M8,256C8,393,119,504,256,504S504,393,504,256,393,8,256,8,8,119,8,256ZM256,440V72a184,184,0,0,1,0,368Z"
transform="translate(-8 -8)"
/>
</svg>
</button>
</div>
</div>
);
}
export default About;

Manually setting Desktop vesion in React

I have a custom Hook useWindowSize that determines whether there is a Mobile or Desktop environment. This is fine for most cases except when a mobile user wants to access the desktop version on his/her phone. I have a button to manually override the current windowsize but am not sure how to approach this.
Here I determine the opposite of the loaded windowsize but how can I switch and reload to the appropriate mode on click?
I will need this mode to stay afterwards even if the window is resized to keep the components linked to either mobile or desktop.
import "./styles.css";
import "./app.scss";
import useWindowSize from "./useWindowSize";
export default function App() {
const windowSize = useWindowSize();
const otherMode = windowSize <= "useMobileVersion" ? "useDesktopVersion" : "useDesktopVersion";
return (
<div className="App">
<p>Right now you are in {windowSize} mode. <button onClick={() => setPageMode("otherMode")}>
Switch to {otherMode} mode
</button>
</p>
</div>
);
}
The codesandbox is here.
The custom Hook is her:
import { useState, useEffect } from "react";
//a Util function that will convert the absolute width into breakpoints
function getBreakPoint(windowWidth) {
if (windowWidth) {
if (windowWidth < 420) {
return "useMobileVersion";
} else {
return "useDesktopVersion";
}
} else {
return "useDesktopVersion";
}
}
function useWindowSize() {
const isWindowClient = typeof window === "object";
const [windowSize, setWindowSize] = useState(
isWindowClient ? getBreakPoint(window.innerWidth) : undefined
);
useEffect(() => {
//a handler which will be called on change of the screen resize
function setSize() {
setWindowSize(getBreakPoint(window.innerWidth));
}
if (isWindowClient) {
//register the window resize listener
window.addEventListener("resize", setSize);
//unregister the listener
return () => window.removeEventListener("resize", setSize);
}
}, [isWindowClient, setWindowSize]);
return windowSize;
}
export default useWindowSize;

Gatsby w/ Chakra UI ColorMode not working

I'm using Gatsby w/ Chakra UI and have an issue with either local storage or how the ColorMode is being accessed.
Here's my repo: https://github.com/RyanPinPB/pdm-gatsby
Live site: https://pearsondigitalmarketing.com
ColorMode and components are styled correctly on localhost, but in production the site has an issue when it renders the header after local storage has saved darkMode=true.
You can reproduce this issue by going to the live site, toggling dark mode, and refreshing. Or, if your browser has theme settings or depending on your OS (or time of day), it will show the issue at night, or if your browser/OS prefers dark mode.
What is weird, is that certain components receive the correct darkMode styling (colorings and background), but my logo, menu, and header background are not correct. Even tho all 3 of these are using colorMode styling conditionals as follows:
const bgColor = {
light: "rgba(255,255,255,.6)",
dark: "rgba(26, 32, 44, .6)",
}
const color = { light: "brand.400", dark: "brand.900" }
bg={bgColor[colorMode]}
color={color[colorMode]}
I added a console log to both index and header files to see if one of them is rendering before the colorMode is triggered/called from local storage, but both console logs print the correct colorMode state. I'm having a hard time figuring out why my header in darkmode has the wrong background color and color styling.
The issue is correct after any click of the "toggle button" in the header. It's just on the initial rendering of the page, if the page thinks it needs to be in DarkMode.
Any help is greatly appreciated.
ThemeContext.js:
import React from "react"
import { ColorModeProvider } from "#chakra-ui/core"
//dont have to use this file if we use Chakra UI
const defaultState = {
dark: false,
toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState)
// Getting dark mode information from OS!
// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
const supportsDarkMode = () =>
window.matchMedia("(prefers-color-scheme: dark)").matches === true
class ThemeProvider extends React.Component {
state = {
dark: false,
}
toggleDark = () => {
console.log("ThemeContext.js: toggle dark/light mode")
let dark = !this.state.dark
localStorage.setItem("dark", JSON.stringify(dark))
this.setState({ dark })
}
componentDidMount() {
// Getting dark mode value from localStorage!
console.log("ThemeContext.js component did mount, dark: " + this.state.dark)
const lsDark = JSON.parse(localStorage.getItem("dark"))
if (lsDark) {
console.log("ThemeContext.js: lsDark: " + lsDark)
this.setState({ dark: lsDark })
} else if (supportsDarkMode()) {
console.log("ThemeContext.js: supports Dark Mode: true")
this.setState({ dark: true })
}
}
render() {
const { children } = this.props
const { dark } = this.state
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: this.toggleDark,
}}
>
<ColorModeProvider>{children}</ColorModeProvider>
</ThemeContext.Provider>
)
}
}
export default ThemeContext
export { ThemeProvider }

React Hook does not work properly on the first render in gatsby production mode

I have the following Problem:
I have a gatsby website that uses emotion for css in js. I use emotion theming to implement a dark mode. The dark mode works as expected when I run gatsby develop, but does not work if I run it with gatsby build && gatsby serve. More specifically the dark mode works only after switching to light and back again.
I have to following top level component which handles the Theme:
const Layout = ({ children }) => {
const [isDark, setIsDark] = useState(() => getInitialIsDark())
useEffect(() => {
if (typeof window !== "undefined") {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark])
return (
<ThemeProvider theme={isDark ? themeDark : themeLight}>
<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>
</ThemeProvider>
)
}
The getInitalIsDark function checks a localStorage value, the OS color scheme, and defaults to false. If I run the application, and activate the dark mode the localStorage value is set. If i do now reload the Application the getInitialIsDark method returns true, but the UI Renders the light Theme. Switching back and forth between light and dark works as expected, just the initial load does not work.
If I replace the getInitialIsDark with true loading the darkMode works as expected, but the lightMode is broken. The only way I got this to work is to automatically rerender after loading on time using the following code.
const Layout = ({ children }) => {
const [isDark, setIsDark] = useState(false)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
if (typeof window !== "undefined" && isReady) {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark, isReady])
useEffect(() => setIsReady(true), [])
useEffect(() => {
const useDark = getInitialIsDark()
console.log("init is dark " + useDark)
setIsDark(useDark)
}, [])
return (
<ThemeProvider theme={isDark ? themeDark : themeLight}>
{isReady ? (<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>) : <div/>}
</ThemeProvider>
)
}
But this causes an ugly flicker on page load.
What am I doing wrong with the hook in the first approach, that the initial value is not working as I expect.
Did you try to set your initial state like this?
const [isDark, setIsDark] = useState(getInitialIsDark())
Notice that I am not wrapping getInitialIsDark() in an additional function:
useState(() => getInitialIsDark())
You will probably crash your build because localStorage is not defined at buildtime. You might need to check if that exists inside getInitialIsDark.
Hope this helps!
#PedroFilipe is correct, useState(() => getInitialIsDark()) is not the way to invoke the checking function on start-up. The expression () => getInitialIsDark() is truthy, so depending on how <ThemedLayout isDark={isDark}> uses the prop it might work by accident, but useState will not evaluate the fuction passed in (as far as I know).
When using an initial value const [myValue, setMyValue] = useState(someInitialValue) the value seen in myValue can be laggy. I'm not sure why, but it seems to be a common cause of problems with hooks.
If the component always renders multiple times (e.g something else is async) the problem does not appear because in the second render the variable will have the expected value.
To be sure you check localstorage on startup, you need an additional useEffect() which explicitly calls your function.
useEffect(() => {
setIsDark(getInitialIsDark());
}, [getInitialIsDark]); //dependency only needed to satisfy linter, essentially runs on mount.
Although most useEffect examples use an anonymous function, you might find more understandable to use named functions (following the clean-code principle of using function names for documentation)
useEffect(function checkOnMount() {
setIsDark(getInitialIsDark());
}, [getInitialIsDark]);
useEffect(function persistOnChange() {
if (typeof window !== "undefined" && isReady) {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark])
I had a similar issue where some styles weren't taking effect because they were being applied to through classes which were set on mount (like you only on production build, everything worked fine in develop).
I ended up switching the hydrate function React was using from ReactDOM.hydrate to ReactDOM.render and the issue disappeared.
// gatsby-browser.js
export const replaceHydrateFunction = () => (element, container, callback) => {
ReactDOM.render(element, container, callback);
};
This is what worked for me, try this and let me know if it works out.
First
In src/components/ i've created a component navigation.js
export default class Navigation extends Component {
static contextType = ThemeContext // eslint-disable-line
render() {
const theme = this.context
return (
<nav className={'nav scroll' : 'nav'}>
<div className="nav-container">
<button
className="dark-switcher"
onClick={theme.toggleDark}
title="Toggle Dark Mode"
>
</button>
</div>
</nav>
)
}
}
Second
Created a gatsby-browser.js
import React from 'react'
import { ThemeProvider } from './src/context/ThemeContext'
export const wrapRootElement = ({ element }) => <ThemeProvider>{element}</ThemeProvider>
Third
I've created a ThemeContext.js file in src/context/
import React, { Component } from 'react'
const defaultState = {
dark: false,
notFound: false,
toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState)
class ThemeProvider extends Component {
state = {
dark: false,
notFound: false,
}
componentDidMount() {
const lsDark = JSON.parse(localStorage.getItem('dark'))
if (lsDark) {
this.setState({ dark: lsDark })
}
}
componentDidUpdate(prevState) {
const { dark } = this.state
if (prevState.dark !== dark) {
localStorage.setItem('dark', JSON.stringify(dark))
}
}
toggleDark = () => {
this.setState(prevState => ({ dark: !prevState.dark }))
}
setNotFound = () => {
this.setState({ notFound: true })
}
setFound = () => {
this.setState({ notFound: false })
}
render() {
const { children } = this.props
const { dark, notFound } = this.state
return (
<ThemeContext.Provider
value={{
dark,
notFound,
setFound: this.setFound,
setNotFound: this.setNotFound,
toggleDark: this.toggleDark,
}}
>
{children}
</ThemeContext.Provider>
)
}
}
export default ThemeContext
export { ThemeProvider }
This should work for you here is the reference I followed from the official Gatsby site

Resources