Utilizing Next.js - Implementation of a unique useThemeMode hook to efficiently manage theme preferences stored in the browser's localStorage, seamlessly integrating with toggleTheme

For a while now, I've been trying to figure out how to toggle my favicon based on the theme logic of my application and the current theme state stored in localStorage. My approach involves using CSS variables and data attributes applied to the html body tag to manage theming effectively. To prevent any theme flickering or inconsistencies, I even injected a script into the html body tag via a custom _document.js file to check for a theme preference in the local storage object before the initial client-side render.

The challenge arose when I attempted to extract the logic from my themeToggle component into a custom hook so that I could consume this data in my favicon component. Unfortunately, I encountered issues with the availability of the document object within the hook I was trying to create

I initially explored managing this with inline styles in an svg/xml file, but Next couldn't properly recognize the inline styles in the SVG. Therefore, my plan was to generate "light" and "dark" versions of my favicon files (both svg and ico), and use a template literal in the href attribute to switch between light and dark file names based on the current theme preference stored in the localStorage object.

Being relatively new to react/nextjs development, I'm aware that there might be methods I haven't considered yet. I believe sharing this logic with a custom hook to consume in both my favicon and themeToggle components should be straightforward, but I can't seem to grasp it just yet. Here is what I have so far. Any guidance on how to effectively implement this would be highly appreciated. This is my first time posting a question, so if anything is unclear, I apologize. Any feedback on how to better formulate questions like these in the future would also be helpful.

ThemeToggle component:-

    import { useState, useEffect } from "react";
    import styled from "styled-components";
    import MoonIcon from "./icons/moonIcon";
    import SunIcon from "./icons/sunIcon";

    const ThemeToggle = () => {
     const [activeTheme, setActiveTheme] = useState(document.body.dataset.theme);
     const inactiveTheme = activeTheme === "light" ? "dark" : "light";

     useEffect(() => {
      document.body.dataset.theme = activeTheme;
      window.localStorage.setItem("theme", activeTheme);
     }, [activeTheme]);

     const toggleClickHandler = () => {
      setActiveTheme(inactiveTheme);
     }

     return (
      <ToggleButton
        type="button"
        aria-label={`Change to ${inactiveTheme} mode`}
        title={`Change to ${inactiveTheme} mode`}
        onClick={() => toggleClickHandler()}
      >
       {activeTheme === "dark" ? <MoonIcon /> : <SunIcon />}
      </ToggleButton>
      );
     };

    export default ThemeToggle;        

Script included in _document.js via dangerouslySetInnerHTML

    const setInitialTheme = `
     function getUserPreference() {
      if(window.localStorage.getItem('theme')) {
       return window.localStorage.getItem('theme')
      }
      return window.matchMedia('(prefers-color-scheme: light)').matches
            ? 'light'
            : 'dark'
    }
    document.body.dataset.theme = getUserPreference();
  `;        

Favicon component where I aim to utilize this logic

    import React, { Fragment } from 'react';

    const Favicon = () => {
    //This is where I would like to consume the hook's logic


    return (
     <Fragment>
      <link rel="icon" href={`/favicon/favicon-${theme}.ico`} sizes="any"/>
      <link rel="icon" type="image/svg+xml" href={`/favicon/favicon-${theme}.svg`} />
      <link
       rel="apple-touch-icon"
       sizes="180x180"
       href="/favicon/apple-touch-icon.png"
      />
      <link rel="manifest" href="/favicon/site.webmanifest" />
      <link
       rel="apple-touch-icon"
       sizes="180x180"
       href="/favicon/apple-touch-icon.png"
      />
      <link
       rel="mask-icon"
       href="/favicon/safari-pinned-tab.svg"
       color="#5bbad5"
      />
      <meta name="apple-mobile-web-app-title" content="Snippit" />
      <meta name="application-name" content="<APP NAME>" />
      <meta name="msapplication-TileColor" content="#ffc40d" />
      <meta name="theme-color" content="#ffffff" />
     </Fragment>
    );
   };

   export default Favicon;

Answer №1

If you ever find yourself stuck in a situation like this, I managed to come up with a solution using the useContext and useEffect hooks. This allowed me to share my theme state across all necessary components and easily make changes to both my UI theme and favicon component:

Implementing Theme Context Component

import { useState, createContext, useEffect } from "react";

const ThemeContext = createContext({
 activeTheme: "",
 inactiveTheme: "",
 toggleTheme: () => {},
});

export const ThemeModeProvider = ({ children }) => {
 const [activeTheme, setActiveTheme] = useState("light");
 const inactiveTheme = activeTheme === "light" ? "dark" : "light";

 const toggleTheme = () => {
  if (activeTheme === "light") {
  setActiveTheme("dark");
  } else {
  setActiveTheme("light");
  }
 };

 useEffect(() => {
  const savedTheme = window.localStorage.getItem("theme");
  savedTheme && setActiveTheme(savedTheme);
 }, []);

 useEffect(() => {
  document.body.dataset.theme = activeTheme;
  window.localStorage.setItem("theme", activeTheme);
  const updateFavicon = async () => {
  const favicon = document.getElementById("favicon");
  if (activeTheme === "light") {
    favicon.href = "/favicon/favicon-light.svg";
  } else {
    favicon.href = "/favicon/favicon-dark.svg";
  }
 };
 updateFavicon();
},[activeTheme]);

return (
 <ThemeContext.Provider
   value={{
    activeTheme,
    inactiveTheme,
    toggleTheme,
   }}
  >
  {children}
  </ThemeContext.Provider>
  );
};

export default ThemeContext;

There might be more optimal solutions out there, but this method allows me to conveniently access my theme context in any future components after setting it up with the ThemeModeProvider wrapper. I found a helpful tutorial on dynamic favicons by Renaissance Engineer
that guided me through implementing the favicon switch based on the theme context.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What methods can I use to gauge the performance of my angular website?

After completing my web project with nodejs and expressjs on the backend and angularjs on the frontend, I am worried about the performance implications. People say that JavaScript can be dangerous if not used correctly, so I ran jshint to verify my synta ...

At what point in time does the LoadingFrameComplete event in Awesomium typically happen?

According to the documentation from Awesomium, the event WebView.LoadingFrameComplete is triggered when a frame finishes loading. This description seems somewhat ambiguous. Does this event coincide with the JavaScript load event of the window? Or perhap ...

When the window is resized, the div shifts position

I recently added a div to my website with the following code: <div id="div"></div> #div { height: 400px; width: 300px; position: absolute; right: 59%; text-align: left; } Howe ...

How can we best store the component's state in the URL in AngularJS?

I am working with a reusable widget that has its own state. This state includes the content of the search bar (2), one or more select boxes (1), and the tree where the user can pick the currently active element (3). My goal is to create a locationManager ...

What is the best way to swap out an HTML file with another without altering the link?

I'm working on an HTML file, which is basically a webpage. My goal is to create a functionality where clicking a button will dynamically replace the content of the page with another HTML file, complete with its own CSS, JavaScript functions, and other ...

Transforming three items into an array with multiple dimensions

There are 3 unique objects that hold data regarding SVG icons from FontAwesome. Each object follows the same structure, but the key difference lies in the value of the prefix property. The first object utilizes fab as its prefix, the second uses far, and t ...

Interested in customizing the hover color of the header links in WordPress?

I'm currently working on customizing the hover color of the links in my WordPress header created with Elementor Page Builder. I tried several CSS solutions by inspecting elements and adding them to the custom CSS section in WordPress, but none seemed ...

Looping in REACT with state updates can cause the values to be overwritten

I'm encountering a problem with my function in React that updates the state. It fetches data from a URL in an array and creates a new item, but when trying to update another state array with this new object, it keeps overriding the first item each tim ...

Changing a single variable into an array that holds the variable in JavaScript

Is there a way to change 5 into [5] using JavaScript? I need this functionality for a method that utilizes jQuery's $.inArray. It should be able to handle both scalar variables and arrays, converting scalars into arrays with a single element. ...

Mastering the art of using div boxes in boxing form

When I box in information using divs, I've noticed that sometimes the wrapping boxes don't fill out the space completely. This can result in elements from other wrappers encroaching into the box area. For example: <div> <div style="le ...

Initiating the Gmail React component for composing messages

Is it possible to use a React application to open mail.google.com and prefill the compose UI with data? This is a requirement that I need help with. ...

Jquery Ajax failing to retrieve a response

Here's the jQuery script I am using to fetch data from my server: $(".login_button").click(function () { var username = $(".username").val(); var userkey = $(".userkey").val(); $.ajax({ type: "GET", url: "http://192.168.0. ...

Autocomplete's `getOptionLabel` function unexpectedly returned an object ([object Object]) instead of the expected string

Currently delving into the world of ReactJS and working with @mui controls, specifically a Multiselect Dropdown with autocomplete feature. Here is the child component causing me some trouble, displaying the following error message: "index.js:1 Materi ...

Retrieve an object using a variable

Essentially, my question is how to extract a value from a variable and input it into a sequence. Being Dutch, I struggle to articulate this query correctly. var channelname = msg.channel.name; "description": `${config.ticketlist.channelname.ticketmessage} ...

Tips for extracting variables from a querystring in Express?

I am trying to retrieve values sent to the server: "/stuff?a=a&b=b&c=c" Can you please advise me on how to extract these values using express? So far, I have attempted... app.get( "/stuff?:a&:b&:c", function( req, res ){}); ...but unfo ...

What could be causing the JQuery Post and Get methods to not respond or execute when they are invoked?

I'm currently working on a project where I need a webpage to automatically open other pages using the post method through a script, without requiring direct user input. I've tried using the JQuery post method, but haven't had any success so ...

How can I delete an individual HTML element that does not have a class or ID?

I am currently working with a theme that cannot be altered due to automatic update requirements. The theme includes the following HTML code snippet: <div class="dropdown-menu"> <a href="#">Profile</a> | <a href="#">Logout</a> ...

Splitting the div into two columns

I've encountered various solutions to this issue, but when I integrate an Angular2 component inside the divs, it fails to function properly. Here is my progress so far: https://i.stack.imgur.com/qJ8a9.jpg Code: <div id="container"> <div ...

Adding and deleting MPEG-DASH segments from a media source buffer in a dynamic manner

I have been developing a custom MPEG-DASH streaming player using the HTML5 video element. Essentially, I am setting up a MediaSource and attaching a SourceBuffer to it. After that, I am appending DASH fragments into this sourcebuffer and everything is func ...

Increase the padding for each child element within its corresponding parent

Is it possible to add padding to child items within each "folder" element without using JavaScript if there is more than one? This scenario would be hardcoded as follows: folder folder inner { padding-left: 14px; } folder folder folder inner { pad ...