'How do I persist my theme to localStorage in NextJs?

I have my theme stored in a stateful function that defaults to false. I then passed a onclick function to toggle between false and true. I want that stored in localstorage and to persist on refresh. But i can't access local storage in nextJS as the initial value.

const [dark, setDark] = useState(false);

  useEffect(() => {
    localStorage.setItem("theme", JSON.stringify(dark));
  }, [dark]);
  useEffect(() => {
    const theme = JSON.parse(localStorage.getItem("theme"));
    if (theme) {
      setDark(theme);
    }
  }, []);
  function setTheme() {
    setDark(!dark);
  }


Solution 1:[1]

There is very famous library with will help to do that with redux.

https://www.npmjs.com/package/redux-persist

You can simply put your theme and pass that reducer to redux persists whitelist. It will help to persist the theme as well as. Single source of truth Concept in React System will also be satisfied.

const persistConfig = { key: 'root', storage: storage, whitelist: ['navigation'] // only navigation Reducer will be persisted };

Solution 2:[2]

Next.js is a server-side rendering framework which means the initial values are from the server. You cannot access local storage from the server until you landed on the client-side.

If you want to have theme data from the server-side you can use nookies which is cookies helpers for NextJs

https://github.com/maticzav/nookies

Your logic maybe like this

useEffect(() => {
    nookies.set({},"theme", JSON.stringify(dark));
  }, [dark]);
useEffect(() => {
    const {theme} = nookies.get({}));
       if (theme) {
         setDark(theme);
       }
  }, []);

Solution 3:[3]

You can use localstorage in Next js inside useEffect hook as it is called only on the client side.

So the localstorage is accessible in useEffect.

I personally don't use libraries for this. Here's how you can do it.

  1. A handler to toggle the theme.
  2. A handler to check theme from localStorage when App component mounts or theme changes.

Use a themeToggler handler in the Navbar component for the theme button.

_app.js


export default function App({Component, pageProps}){
  const [darkMode, setDarkMode] = useState(false);

  // check and reset theme when `darkMode` changes
  useEffect(() => {
    themeCheck();
  }, [darkMode]);

  // check theme on component mount
  useEffect(() => {
    themeCheck();
  }, []);

  // check and reset theme
  const themeCheck = () => {
    if (
      localStorage.theme === "dark" ||
      (!("theme" in localStorage) &&
        window.matchMedia("(prefers-color-scheme: dark)").matches)
    ) {
      document.documentElement.classList.add("dark");
      setDarkMode(true);
    } else {
      document.documentElement.classList.remove("dark");
      setDarkMode(false);
    }
  }
  return (
      <>
          <Navbar darkMode={darkMode} setDarkMode={setDarkMode}/>
          <Component {...pageProps} />
      </>
  );

Navbar.js

export default function Navbar({ darkMode, setDarkMode }) {
  // called when theme button is pressed
  const toggleTheme = () => {
    const theme = localStorage.getItem("theme");
    if (theme) {
      localStorage.setItem("theme", theme === "dark" ? "light" : "dark");
    } else {
      localStorage.setItem("theme", "dark");
    }
    setDarkMode(!darkMode);
  };

  return (
    <>
      <button id="themeBtn" onClick={toggleTheme}>
        Change theme
      </button>
    </>
  );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Diptom Saha
Solution 2
Solution 3 Amit