Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 13, 2022 07:42 pm GMT

Implementing Dark Mode in React

Hola folks! These days we all want to have the dark mode feature in our websites and applications. And why shouldn't we? It is more soothing to the eyes of the user, and we as UI/UX developers should tend to every need of our user.

But, how do we implement this dark mode feature in React? There are many things a developer is supposed to take care of while implementing this feature:

  • User Preference
    • Use the system preference if the user is visiting for the first time.
    • Use the user-preferred theme if the user has set it before.
    • Store the user-preferred theme.
  • Toggle theme preference
    • Users should be able to toggle between different themes.
  • Avoiding the Flicker
    • This flicker is eye-blinding and gives a bad user experience.
  • Access to the theme
    • The theme should be easily accessible across the whole application.

Lets cater to the points mentioned above one by one & learn how to implement the dark mode feature in React.

User Preference

System-wide Theme Preference

Let us first try to access the users system-wide theme preference. We can do this with the help of the prefers-color-scheme media feature. We can pass this media feature with the theme values light & dark to know if the user has set any system-wide theme preference.

Now, we use the matchMedia window method to check whether the document matches the passed media query string.

const preferColorSchemeQuery = "(prefers-color-scheme: dark)";const theme = matchMedia(preferColorSchemeQuery).matches ? "dark" : "light";

User-preferred Theme

In a case where the user has already visited our application & has set some theme preference, we need to store this theme preference & retrieve it every time the user visits our application. We will use the local storage to store the users theme preference.

localStorage.setItem("theme", "dark"); // or "light"localStorage.getItem("theme");

This user-preferred theme is to be given priority over the system-wide theme preference. Therefore, the code will look as follows:

const preferColorSchemeQuery = "(prefers-color-scheme: dark)";const theme = localStorage.getItem("theme") ||     (matchMedia(preferColorSchemeQuery).matches ? "dark" : "light");

Toggle theme preference

The user should be able to toggle between different themes. This feature can be easily provided with the help of a checkbox input & a theme state.

// App.jsconst preferColorSchemeQuery = "(prefers-color-scheme: dark)";const giveInitialTheme = () =>     localStorage.getItem("theme") ||     (matchMedia(preferColorSchemeQuery).matches ? "dark" : "light");const App = () => {    const [theme, setTheme] = useState(giveInitialTheme());    const toggleTheme = () =>         setTheme((theme) => (theme === "light" ? "dark" : "light"));    useEffect(() => {        localStorage.setItem("theme", theme);    }, [theme]);    return (        <input      type="checkbox"      name="theme-toggle"      id="theme-toggle"      checked={theme && theme === "dark"}      onChange={toggleTheme}        />    );}

Here, we also have to make sure to update the local storage value of the theme. We do this with the help of the useEffect hook. useEffect runsafter React renders the component and ensures that the effect callback does not block the browsers visual painting.

Avoiding the flicker

To avoid the famous flicker we need to perform the DOM updates before React renders the component & the browser paints the visual screen. But, as we have seen above useEffect can only help us perform operations after the render has been committed to the screen. Hence, the fli*c*ker.

Let me introduce you to another hook, useLayoutEffect. The syntax for this hook is identical to that of useEffect. The callback passed to this hook runs synchronously immediately after React has performed all DOM mutations. The code runs immediately after the DOM has been updated, but before the browser has had a chance to paint those changes.

Warning
Prefer the standarduseEffect when possible to avoid blocking visual updates.

So, we will be performing our updates with the help of useLayoutEffect.

What updates?

We will have to update our CSS to match the current theme. Seems like a big task, doesnt it? There are many ways to update the CSS, but, we will go forward with the most efficient way, i.e. CSS Variables or Custom Properties.

CSS variables are entities defined by CSS authors that contain specific values to be reused throughout a document. They are set using custom property notation (e.g.,--main-color: black;) and are accessed using the[var()](https://developer.mozilla.org/en-US/docs/Web/CSS/var)function (e.g.,color: var(--main-color);).

We can also use HTML data-* attributes with CSS to match the data attribute & apply styles accordingly. In our case, depending on the data-theme attribute value, different colors will be applied to our page.

/* index.css */[data-theme="light"] {    --color-foreground-accent: #111111;    --color-foreground: #000000;    --color-background: #ffffff;}[data-theme="dark"] {    --color-foreground-accent: #eeeeee;    --color-foreground: #ffffff;    --color-background: #000000;}.app {    background: var(--color-background);    color: var(--color-foreground);}

Our application code now will look something like this:

// App.jsconst preferColorSchemeQuery = "(prefers-color-scheme: dark)";const giveInitialTheme = () =>     localStorage.getItem("theme") ||     (matchMedia(preferColorSchemeQuery).matches ? "dark" : "light");const App = () => {    const [theme, setTheme] = useState(giveInitialTheme());    const toggleTheme = () =>         setTheme((theme) => (theme === "light" ? "dark" : "light"));    useEffect(() => {        localStorage.setItem("theme", theme);    }, [theme]);    useLayoutEffect(() => {    if (theme === "light") {      document.documentElement.setAttribute("data-theme", "light");    } else {      document.documentElement.setAttribute("data-theme", "dark");    }  }, [theme]);    return (        <input      type="checkbox"      name="theme-toggle"      id="theme-toggle"      checked={theme && theme === "dark"}      onChange={toggleTheme}        />    );}

Access to the theme

The theme value might be needed anywhere across the application. We have to take care of this too. For this purpose, we store our theme value in a context & wrap its provider around the App component.

// theme-context.js// create theme contextconst ThemeContext = createContext();const preferColorSchemeQuery = "(prefers-color-scheme: dark)";const giveInitialTheme = () =>     localStorage.getItem("theme") ||     (matchMedia(preferColorSchemeQuery).matches ? "dark" : "light");// theme context providerconst ThemeProvider = ({ children }) => {    const [theme, setTheme] = useState(giveInitialTheme());    const toggleTheme = () =>         setTheme((theme) => (theme === "light" ? "dark" : "light"));    useEffect(() => {        localStorage.setItem("theme", theme);    }, [theme]);    useLayoutEffect(() => {    if (theme === "light") {      document.documentElement.setAttribute("data-theme", "light");    } else {      document.documentElement.setAttribute("data-theme", "dark");    }  }, [theme]);    return (    <ThemeContext.Provider value={{ theme, toggleTheme }}>      {children}    </ThemeContext.Provider>  );}// custom hook to avail theme valueconst useTheme = () => {  const context = useContext(ThemeContext);  if (context === undefined) {    throw new Error("useTheme must be used within a ThemeProvider");  }  return context;};// exportsexport { ThemeProvider, useTheme };

Congratulations! We are done with the implementation. You now know how to implement Dark Mode in your React application. Go and implement this super cool feature in your application now.

Extra feature

Consider a case, where the user changes the system-wide theme preference while he/she is using your application. In the implementation above, the application wont be able to detect these changes. If you want your application to detect these changes, we will need to set up a change event listener on this system-wide theme preference. We can do this with the help of the useEffect hook.

useEffect(() => {    const mediaQuery = matchMedia(preferColorSchemeQuery);    const handleColorSchemeChange = () =>      setTheme(mediaQuery.matches ? "dark" : "light");    mediaQuery.addEventListener("change", handleColorSchemeChange);    return () =>      mediaQuery.removeEventListener("change", handleColorSchemeChange);}, []);

We add a change event listener to the mediaQuery on the mount. The final theme context will look something like this:

// theme-context.js// create theme contextconst ThemeContext = createContext();const preferColorSchemeQuery = "(prefers-color-scheme: dark)";const giveInitialTheme = () =>     localStorage.getItem("theme") ||     (matchMedia(preferColorSchemeQuery).matches ? "dark" : "light");// theme context providerconst ThemeProvider = ({ children }) => {    const [theme, setTheme] = useState(giveInitialTheme());    const toggleTheme = () =>         setTheme((theme) => (theme === "light" ? "dark" : "light"));    useEffect(() => {        const mediaQuery = matchMedia(preferColorSchemeQuery);        const handleColorSchemeChange = () =>          setTheme(mediaQuery.matches ? "dark" : "light");        mediaQuery.addEventListener("change", handleColorSchemeChange);        return () =>          mediaQuery.removeEventListener("change", handleColorSchemeChange);    }, [])    useEffect(() => {        localStorage.setItem("theme", theme);    }, [theme]);    useLayoutEffect(() => {    if (theme === "light") {      document.documentElement.setAttribute("data-theme", "light");    } else {      document.documentElement.setAttribute("data-theme", "dark");    }  }, [theme]);    return (    <ThemeContext.Provider value={{ theme, toggleTheme }}>      {children}    </ThemeContext.Provider>  );}// custom hook to avail theme valueconst useTheme = () => {  const context = useContext(ThemeContext);  if (context === undefined) {    throw new Error("useTheme must be used within a ThemeProvider");  }  return context;};// exportsexport { ThemeProvider, useTheme };

You can refer to the Codesandbox below:

Please feel free to share your feedback in the comments section. You can connect with me on Twitter or LinkedIn.

Happy hacking! Keep learning!

References


Original Link: https://dev.to/horsemaker/implementing-dark-mode-in-react-17he

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To