Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle - React/NextJS 13 (with hot fix) #30

Open
Biratus opened this issue Feb 1, 2023 · 10 comments
Open

Toggle - React/NextJS 13 (with hot fix) #30

Biratus opened this issue Feb 1, 2023 · 10 comments

Comments

@Biratus
Copy link

Biratus commented Feb 1, 2023

Hello,

I just started using DaisyUI and tailwindcss. I want to say first and foremost that I am not an expert (with these libraries).
None of the examples or answers worked for me so I checked the code to see what is going on. And I want to share my findings and my solution.

The themeChange(false) did nothing so I investigated and put the themeToggle function in my useEffect and especially the last part.
I added a return function to the useEffect to unsubscribe, with React 18 the listener is called twice so the toggle is useless (i.e. changes theme and reverts back to the initial one => No visual change on the page).

And that is all actually.

Here is my toggle component (I am using react-feather for icons):

"use client";
import { useEffect } from "react";
import { Moon, Sun } from "react-feather";

export default function SwitchTheme({}) {
    
  useEffect(() => {
    [...document.querySelectorAll("[data-toggle-theme]")].forEach((el) => {
      el.addEventListener("click", toggleTheme);
    });

    return () =>
      [...document.querySelectorAll("[data-toggle-theme]")].forEach((el) =>
        el.removeEventListener("click", toggleTheme)
      );
  }, []);

  return (
    <div className="flex gap-2">
      <Sun />
      <input
        type="checkbox"
        className="toggle"
        data-toggle-theme="light,dark"
      />
      <Moon />
    </div>
  );
}

function toggleTheme(evt: any) {
  var themesList = evt.target.getAttribute("data-toggle-theme");
  if (themesList) {
    var themesArray = themesList.split(",");
    if (document.documentElement.getAttribute("data-theme") == themesArray[0]) {
      if (themesArray.length == 1) {
        document.documentElement.removeAttribute("data-theme");
        localStorage.removeItem("theme");
      } else {
        document.documentElement.setAttribute("data-theme", themesArray[1]);
        localStorage.setItem("theme", themesArray[1]);
      }
    } else {
      document.documentElement.setAttribute("data-theme", themesArray[0]);
      localStorage.setItem("theme", themesArray[0]);
    }
  }
}

tailwind.config.js if needed:

module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",

    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require("daisyui")],
  darkMode: ["class", '[data-theme="dark"]'],
  daisyui: {
    themes: ["light", "dark"],
  },
};

This is a hot fix for the problem I had. As I said I am in no way an expert with these libraries, just wanted to share so you guys could try and implement this in a nice way :) The BIG problem is regarding useEffect and React 18 that calls it twice I think.

Keep me updated if I have done blasphemous stuff with this code... I might take a look in the futur but it works for now..
Cheers !

@Biratus Biratus changed the title Toggle - React/NextJS 13 Toggle - React/NextJS 13 (with hot fix) Feb 1, 2023
@yaffalhakim1
Copy link

man you save my life right here. Thank you for the hot fix

@darkterminal
Copy link

darkterminal commented Mar 7, 2023

@Biratus dude you deserve 🍻 true freestyler!

@trycoast
Copy link

trycoast commented Apr 1, 2023

Are there any plans for fixing this? It's been an issue for a long time. I feel like the provided solution renders the library itself somewhat pointless.

@trycoast
Copy link

trycoast commented Apr 1, 2023

Anyways, as inferred by OP, the cause is React's strictmode mounting all components twice.

The following solution worked for me.

useEffect(() => {
    themeChange(false);
    return () => {
      themeChange(false);
    };
  }, []);

This should work with both strictmode enabled as well as in production.

@saadeghi
Copy link
Owner

saadeghi commented Apr 5, 2023

The following solution worked for me.

useEffect(() => {
    themeChange(false);
    return () => {
      themeChange(false);
    };
  }, []);

Thanks 👍
I haven't used Next.js 13 yet and I don't know about major changes. I'm guessing this strictmode you mentioned is a new default? and is it forcing components to render on the server instead of client?
Because if that's the case, it would be a problem with a lot of components out there. this script works with localStorage so it's expected to run only on the client

@trycoast
Copy link

trycoast commented Apr 5, 2023

I don't think this relates to Next.js at all (I have never used it). Strictmode is a React artifact, and yes, it is enabled by default in the newer versions (the general consensus seem to be that disabling strictmode is not recommended). The problem with most solutions I tried was that they'd work with strictmode on, and thus break in production, or work with strictmode off, and thus break in development. The above, however, seems to work in both scenarios.

@dorukgezici
Copy link

Anyways, as inferred by OP, the cause is React's strictmode mounting all components twice.

The following solution worked for me.

useEffect(() => {
    themeChange(false);
    return () => {
      themeChange(false);
    };
  }, []);

This should work with both strictmode enabled as well as in production.

This should be included on the README for NextJS

@rcapdepaula
Copy link

rcapdepaula commented Oct 18, 2023

import { useEffect } from 'react';
import { themeChange } from 'theme-change';

export default function MyApp() {
useEffect(() => {
themeChange(false);
return () => {
themeChange(false);
};
}, []);

return (
// Your JSX here
);
}

// button

<button data-toggle-theme="dark,light" className="btn btn-primary"> Toggle Theme </button>

// daisy ui config

plugins: [require('daisyui')],
daisyui: {
themes: ['dark', 'light'],
},

dont forget to use 'use client' on first line

@laxmariappan
Copy link

@dorukgezici @rcapdepaula thanks for the snippets; it works.

My system preference is dark bg, so when I choose the light mode and refresh the app, I see dark bg for a second while it sets the light mode.

How to get this working? instead of the button

<label className="flex cursor-pointer gap-2">
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/></svg>
  <input type="checkbox" value="synthwave" className="toggle theme-controller"/>
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
</label>

@laxmariappan
Copy link

Here is my full component; there might be a better way to do this.
It works fine, as expected

"use client";

import { useEffect, useState } from "react";
import { themeChange } from "theme-change";

export default function Header() {

const initialTheme = window.localStorage.getItem("theme") || "light";
const [theme, setTheme] = useState(initialTheme);

const handleThemeChange = () => {

  const currentTheme = window.localStorage.getItem("theme");
  currentTheme === "dark" ? setTheme("dark") : setTheme("light");
};

useEffect(() => {
  themeChange(false);
  return () => {
    themeChange(false);
  };
}, []);

    return (
      <>
        <div className="navbar bg-base-100">
          <div className="flex-1">
            <a className="btn btn-ghost text-xl">daisyUI</a>
          </div>
          <div className="flex-none">
            <ul className="menu menu-horizontal px-1">
              <li>
                <a>Link</a>
              </li>
              <li>
                <details>
                  <summary>Parent</summary>
                  <ul className="p-2 bg-base-100">
                    <li>
                      <a>Link 1</a>
                    </li>
                    <li>
                      <a>Link 2</a>
                    </li>
                  </ul>
                </details>
              </li>
            </ul>
            <button
              data-toggle-theme="dark,light"
              onClick={handleThemeChange}
              className="btn btn-primary"
            >
              {theme === "light" ? (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                >
                  <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                </svg>
              ) : (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                >
                  <circle cx="12" cy="12" r="5" />
                  <path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
                </svg>
              )}
            </button>
          </div>
        </div>
      </>
    );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants