Search code examples
reactjsuse-statethemeprovider

How to resolve root rendering issue with window size set in useState after server render?


After deployment to server and testing in Gatsby with gatsby clean && gatsby build && gatsby serve whenever root (https://foobar.com/) is visited anything that relies on my ThemeProvider for dimensions doesn't render correctly.

After debugging I've isolated my issue down to how my useState is hard set with a value of 0:

 const [size, setSize] = useState({
    windowWidth: hasWindow ? window.innerWidth : 0,
    windowHeight: hasWindow ? window.innerHeight : 0,
  })

full code:

import { useState, useEffect } from 'react'

const hasWindow = typeof window !== 'undefined'

const useWindowDimensions = () => {
  const [size, setSize] = useState({
    windowWidth: hasWindow ? window.innerWidth : 0,
    windowHeight: hasWindow ? window.innerHeight : 0,
  })

  const updateSize = () =>
    setSize({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight,
    })

  useEffect(() => (window.onresize = updateSize), [])

  return size
}

export default useWindowDimensions

which gets passed to my ThemeProvider:

import React, { createContext } from 'react'

// Utils
import useWindowDimensions from '../utils/useWindowDimensions'

const defaultContext = {
  windowWidth: 0,
  windowHeight: 0,
}

export const ThemeContext = createContext(defaultContext)

const ThemeProvider = ({ children }) => {
  const { windowWidth, windowHeight } = useWindowDimensions()

  return (
    <ThemeContext.Provider value={{ windowWidth, windowHeight }}>{children}</ThemeContext.Provider>
  )
}

export default ThemeProvider

and if I hard set my useState values:

  const [size, setSize] = useState({
    windowWidth: hasWindow ? window.innerWidth : 1600,
    windowHeight: hasWindow ? window.innerHeight : 1600,
  })

my site renders fine in the browser but this is technically wrong if viewed with a mobile device or the browser is smaller than 1600px. Whenever I navigate to anywhere else in the site the useState is updated and there isn't an issue.

Omitting the ternary check for window and hard setting useState to:

const [size, setSize] = useState({
  windowWidth: window.innerWidth,
  windowHeight: window.innerHeight,
})

produces an expected server rendering error:

"window" is not available during server side rendering.

after gatsby build.

How should I be setting my useState window dimensions after server load so that my site will pass the correct values to the ThemeProvider and render everything correctly when root is visited?


Solution

  • Please try this slight modification and let me know if that works. In your useWindowDimensions custom hook:

    import { useState, useEffect } from 'react'
    
    const useWindowDimensions = () => {
      const [size, setSize] = useState({
        windowWidth: undefined,
        windowHeight: undefined
      })
    
      const updateSize = () =>
        setSize({
          windowWidth: window.innerWidth,
          windowHeight: window.innerHeight,
        })
    
      useEffect(() => {
        window.addEventListener("resize", updateSize);
        updateSize();
    
        return () => window.removeEventListener("resize", updateSize);
      }, [])
    
      return size
    }
    
    export default useWindowDimensions
    

    Your Context:

    const defaultContext = {
      windowWidth: undefined,
      windowHeight: undefined,
    }