Warm tip: This article is reproduced from stackoverflow.com, please click
javascript reactjs frontend

How do I make sure my first render with React has a value from localStorage?

发布于 2020-03-27 10:28:50

My website has a light and dark theme. The default theme is light. If the user changes the theme to dark, it is saved to localStorage.

On the next visit/refresh at the root of my component tree, this code runs:

  useLayoutEffect(() => {
    let storedTheme = localStorage.getItem("theme");

    if (storedTheme === "light" || storedTheme === "dark") {

    // Redux action. Other components subscribe to the theme.
      setTheme(storedTheme);
    }
  }, [setTheme]);

Say the user chose the dark theme as their preference. It works fine. However, the first render will be the light theme. The second render will be the dark theme. This causes a light to dark flicker when visiting the site.

Is there a way to ensure my first render has the set value from localStorage?

Questioner
Osama Qarem
Viewed
22
Osama Qarem 2019-07-03 23:22

What worked in getting data to my first render was initializing the redux store like so:

if (typeof localStorage != "undefined") {
  initialState = {
    theme: localStorage.getItem("theme"),
  };
} else {
  initialState = {};
}

const store = createStore(reducers, initialState);

But it worsened the situation as the components no longer re-rendered on first load because the theme prop from redux never changed because the store was initialized with the user preference. So I went back to the old approach with in the original question above.

The flicker happens because I am using Javascript to control the theme in my react app. I am also using Gatsby which generates a static version of my site that loads before my react app.

Loading the site goes like this:

  1. E.g. User with dark theme preference visits. They see static content without any Javascript while JS bundles download in the background.

  2. Since light theme is the default, the static version of the site will be light.

  3. Javascript loads and React's first render happens and the redux store is intialized. The root component gets the theme from localStorage and the tree re-renders with the dark theme.

The flicker happens because of the events between 2 and 3.

I didn't manage to solve the issue completely. But I managed to alleviate the flicker to be much more unnoticable by enabling Gatsby's service worker. The worker caches Javascript bundles for next site visit, and loading Javascript from disk means the flicker will linger for a shorter amount of time as the JS bundles are already available for execution.