rodrigo lemus
rodrigo lemus

Reputation: 31

How manage Theme value persistent in QwikJS

I'd like to handle the light/dark theme, but the code does not run on browser the first time

useTask$(({ track }) => {
    track(() => themeAppearance.value);
    if (isBrowser) {
      const savedTheme = localStorage.getItem("themeApp");
      console.log("from local storage", savedTheme);
      if (savedTheme) {
        themeAppearance.value =
          savedTheme === "dark" ? ThemeTypes.Dark : ThemeTypes.Ligth;
        return;
      }
    }
  });

only if i change the themeAppearance with the button with this function from useThemeHook

const toggleAppearance$ = $(() => {
    console.log(themeAppearance.value);

    if (themeAppearance.value === ThemeTypes.Dark) {
      themeAppearance.value = ThemeTypes.Ligth;
      backGroundPrimaryColor.value = BackdrounColor.Ligth;
      backGroundSecondaryColor.value = BackdrounColor.Ligth;

      textColorPrimary.value = TextColor.HardTextLigthBackground;
      textColorSecondary.value = TextColor.LigthTextLigthBackground;

      localStorage.setItem("themeApp", ThemeTypes.Ligth);
      return;
    }

    if (
      themeAppearance.value === ThemeTypes.Ligth &&
      panelBackground === "solid"
    ) {
      themeAppearance.value = ThemeTypes.Dark;
      backGroundPrimaryColor.value = BackdrounColor.DarkPrimarySolid;
      backGroundSecondaryColor.value = BackdrounColor.DarkSecondarySolid;

      textColorPrimary.value = TextColor.HardTextDarkBackground;
      textColorSecondary.value = TextColor.LigthTextDarkBackground;

      localStorage.setItem("themeApp", ThemeTypes.Dark);

      return;
    }

    if (
      themeAppearance.value === ThemeTypes.Ligth &&
      panelBackground === "translucent"
    ) {
      themeAppearance.value = ThemeTypes.Dark;
      backGroundPrimaryColor.value = BackdrounColor.DarkPrimaryTranslucent;
      backGroundSecondaryColor.value = BackdrounColor.DarkSecondaryTranslucent;

      textColorPrimary.value = TextColor.HardTextDarkBackground;
      textColorSecondary.value = TextColor.LigthTextDarkBackground;

      localStorage.setItem("themeApp", ThemeTypes.Dark);

      return;
    }
  });

I tried using useVisibleTask$() but it doesn't work for me because it runs after the first render so it's going to be a bad user experience its first render with default theme dark and then change to light saved theme

Upvotes: 0

Views: 355

Answers (2)

eldss
eldss

Reputation: 11

I've been experimenting with this myself. There is a hint in the Qwik docs Cookbook section. However, it leaves something to be desired in terms of detail and flexibility.

I am using Tailwind, as in the example, but the idea should at least be a starting point for your case. In short, to avoid the flash of un-themed content, you have to add the startup code to a script tag in the document head, in root.tsx. To do this, you'll have to add a string representing the code with dangerouslySetInnerHTML.

<script dangerouslySetInnerHTML={code-string-here}></script>

In the Qwik docs example, they just copy and paste all the code, unformatted, in a string. This should work, but is pretty ugly, and just feels bad.

But since Qwik uses Vite as a build tool, there is a better way!

In your case, create a file like ./utils/setInitialTheme.js and add your code:

const savedTheme = localStorage.getItem("themeApp");
console.log("from local storage", savedTheme);
if (savedTheme) {
  themeAppearance.value =
      savedTheme === "dark" ? ThemeTypes.Dark : ThemeTypes.Ligth;
  return;
}

However, you'd have to come up with a different strategy than using signals initially. You couldn't use themeAppearance.value here. And you'd have to import ThemeTypes.

Then in root.tsx:

// ?raw imports the file as a raw string
...
import setInitialThemeAsString from "./utils/setInitialTheme.js?raw"
...

...
<head>
  <meta charSet="utf-8" />
  <link rel="manifest" href="/manifest.json" />
  <RouterHead />
  <script dangerouslySetInnerHTML={setInitialThemeAsString}></script>
</head>
...

That is all I had to do to get this working without any flash.

For the sake of completeness, I'll add for anyone coming to this problem using Tailwind: Simply use the code they have in the Tailwind docs:

if (localStorage.theme === 'dark' || 
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
  document.documentElement.classList.add('dark')
} else {
  document.documentElement.classList.remove('dark')
}

Then you can use whatever makes sense for your app to react to users changing the theme.

=== EDIT ===

After working on my project more I did uncover an issue with the solution above using the Vite ?raw technique. This worked fine in development, but I encountered build errors running a production build. They had something to do with Rollup and I couldn't quite figure out how to solve it. So, if you want this to work in prod, you may have to move the code out of a separate file and into a string constant in the root.tsx file. That will work as expected in prod as well.

If interested in an in-depth tutorial, and a working example, feel free to check out my personal website/blog: https://eldss.dev/blog/build-a-theme-toggle-in-qwik-pt-one/

Upvotes: 1

cmolina
cmolina

Reputation: 1378

Did you try enabling eagerness?

useVisibleTask$(
  () => {
    const savedTheme = localStorage.getItem("themeApp");
    console.log("from local storage", savedTheme);
    if (savedTheme) {
      themeAppearance.value =
        savedTheme === "dark" ? ThemeTypes.Dark : ThemeTypes.Ligth;
      return;
    }
  },
  { strategy: 'document-ready' }
);

Upvotes: 0

Related Questions