Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Hook does not work properly on the first render in gatsby production mode

I have the following Problem:

I have a gatsby website that uses emotion for css in js. I use emotion theming to implement a dark mode. The dark mode works as expected when I run gatsby develop, but does not work if I run it with gatsby build && gatsby serve. More specifically the dark mode works only after switching to light and back again.

I have to following top level component which handles the Theme:

const Layout = ({ children }) => {
  const [isDark, setIsDark] = useState(() => getInitialIsDark())

  useEffect(() => {
    if (typeof window !== "undefined") {
      console.log("save is dark " + isDark)
      window.localStorage.setItem("theming:isDark", isDark.toString())
    }
  }, [isDark])

  return (
    <ThemeProvider theme={isDark ? themeDark : themeLight}>
      <ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>
    </ThemeProvider>
  )
}

The getInitalIsDark function checks a localStorage value, the OS color scheme, and defaults to false. If I run the application, and activate the dark mode the localStorage value is set. If i do now reload the Application the getInitialIsDark method returns true, but the UI Renders the light Theme. Switching back and forth between light and dark works as expected, just the initial load does not work.

If I replace the getInitialIsDark with true loading the darkMode works as expected, but the lightMode is broken. The only way I got this to work is to automatically rerender after loading on time using the following code.

const Layout = ({ children }) => {
  const [isDark, setIsDark] = useState(false)
  const [isReady, setIsReady] = useState(false)

  useEffect(() => {
    if (typeof window !== "undefined" && isReady) {
      console.log("save is dark " + isDark)
      window.localStorage.setItem("theming:isDark", isDark.toString())
    }
  }, [isDark, isReady])

  useEffect(() => setIsReady(true), [])
  useEffect(() => {
    const useDark = getInitialIsDark()
    console.log("init is dark " + useDark)
    setIsDark(useDark)
  }, [])

  return (
    <ThemeProvider theme={isDark ? themeDark : themeLight}>
      {isReady ? (<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>) : <div/>}
    </ThemeProvider>
  )
}

But this causes an ugly flicker on page load.

What am I doing wrong with the hook in the first approach, that the initial value is not working as I expect.

like image 921
quadroid Avatar asked May 12 '20 17:05

quadroid


People also ask

Can I use React Hooks with Gatsby?

At its core, Gatsby uses vanilla React with all its features. So this means Hooks are available to use with a simple import statement.

Does useEffect run on first render?

By default, useEffect will run on initial render as well as every future render (update) of your component.

Why React Hooks are the wrong abstraction?

React uses lint rules and will throw errors to try to prevent developers from violating this detail of Hooks. In this sense, React allows the developer to make mistakes and then tries to warn the user of their mistakes afterward.

Does the order of React Hooks matter?

The answer is that React relies on the order in which Hooks are called. Our example works because the order of the Hook calls is the same on every render: // ------------ // First render // ------------ useState('Mary') // 1.


1 Answers

Did you try to set your initial state like this?

const [isDark, setIsDark] = useState(getInitialIsDark())

Notice that I am not wrapping getInitialIsDark() in an additional function:

useState(() => getInitialIsDark())

You will probably crash your build because localStorage is not defined at buildtime. You might need to check if that exists inside getInitialIsDark.

Hope this helps!

like image 91
Pedro Filipe Avatar answered Nov 15 '22 04:11

Pedro Filipe