fix useState useEffect which is read twice

66 views Asked by At

here I had a problem, first I made a theme change using Tailwind CSS + daisyUI it seemed there was no problem until I found out that when changing the theme and refreshing the browser it flashed for a moment

And I looked at the console log and it turned out it was in this code

const [theme, setTheme] = useState("light");

if I change it to light then the dark theme flashes and vice versa but when I useState("") nothing it becomes <empty string>

I believe that the flickering I am experiencing is the problem from this, how can I fix it, any help is welcome

"use client";

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

export const ThemeContext = createContext();
export const ThemeProvider = ({ children }: any) => {
  const [theme, setTheme] = useState("");
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    const storedTheme = localStorage.getItem("theme") || "light";
    setTheme(storedTheme);

    const intervalId = setInterval(() => {
      setIsMounted(true);
    }, 1000);
  }, []);

  if (!isMounted) {
    console.log(theme);

    return (
      <div data-theme={theme} className="fixed top-0 left-0 w-full h-full z-50">
        <div className="text-blue-500 opacity-75 top-1/2 mx-auto block relative w-0 h-0">
          <span className="loading loading-dots loading-lg -ml-5"></span>
        </div>
      </div>
    );
  }

  const changeTheme = (theme: string) => {
    setTheme(theme);
    localStorage.setItem("theme", theme);
  };

  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
const [theme, setTheme] = useState("");

results from console log :

enter image description here

const [theme, setTheme] = useState("light");

results from console log :

enter image description here

1

There are 1 answers

2
Hashan Hemachandra On

According to your comment given, the error happens because localStorage is a browser specific feature not available in Node.js environment. You need to ensure that localStorage is accessed only after the component has been mounted, which means it's in the client-side environment. I have modified the answer,

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

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light"); // Default theme can be 'light'
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true); // This will be set to true once the component has been mounted
    // Now, since we're sure this code is running in the browser, access localStorage
    const storedTheme = localStorage.getItem("theme") || "light";
    setTheme(storedTheme); // Apply the stored theme
  }, []);

  useEffect(() => {
    if (isMounted) {
      localStorage.setItem("theme", theme);
      document.documentElement.setAttribute('data-theme', theme);
    }
  }, [theme, isMounted]);

  const changeTheme = (newTheme) => {
    setTheme(newTheme);
  };

  if (!isMounted) {
    return null; // Prevent rendering the children until the component has mounted
  }

  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};