reactjstypescriptnext.jsstring-interpolationrecoiljs

nextjs react recoil persist values in local storage: initial page load in wrong state


I have the following code,

const Layout: React.FC<LayoutProps> = ({ children }) => {
    const darkMode = useRecoilValue(darkModeAtom)
    
    console.log('darkMode: ', darkMode)
    return (
        <div className={`max-w-6xl mx-auto my-2 ${darkMode ? 'dark' : ''}`}>
            <Nav />
            {children}
            <style jsx global>{`
                body {
                    background-color: ${darkMode ? '#12232e' : '#eefbfb'};
                }
            `}</style>
        </div>
    )
}

I am using recoil with recoil-persist. So, when the darkMode value is true, the className should include a dark class, right? but it doesn't. I don't know what's wrong here. But it just doesn't work when I refresh for the first time, after that it works fine. I also tried with darkMode === true condition and it still doesn't work. You see the styled jsx, that works fine. That changes with the darkMode value and when I refresh it persists the data. But when I inspect I don't see the dark class in the first div. Also, when I console.log the darkMode value, I see true, but the dark class is not included.

Here's the sandbox link

Maybe it's a silly mistake, But I wasted a lot of time on this. So what am I doing wrong here?


Solution

  • The problem is that during SSR (server side rendering) there is no localStorage/Storage object available. So the resulted html coming from the server always has darkMode set to false. That's why you can see in cosole mismatched markup errors on hydration step.

    I'd assume using some state that will always be false on the initial render (during hydration step) to match SSR'ed html but later will use actual darkMode value. Something like:

    // themeStates.ts
    import * as React from "react";
    import { atom, useRecoilState } from "recoil";
    import { recoilPersist } from "recoil-persist";
    
    const { persistAtom } = recoilPersist();
    
    export const darkModeAtom = atom<boolean>({
      key: "darkMode",
      default: false,
      effects_UNSTABLE: [persistAtom]
    });
    
    export function useDarkMode() {
      const [isInitial, setIsInitial] = React.useState(true);
      const [darkModeStored, setDarkModeStored] = useRecoilState(darkModeAtom);
    
      React.useEffect(() => {
        setIsInitial(false);
      }, []);
    
      return [
        isInitial === true ? false : darkModeStored,
        setDarkModeStored
      ] as const;
    }
    

    And inside components use it like that:

    // Layout.tsx
      const [darkMode] = useDarkMode();
    // Nav.tsx
      const [darkMode, setDarkMode] = useDarkMode();
    

    codesandbox link