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;