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

109 views Asked by At

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?

1

There are 1 answers

1
ivanatias On BEST ANSWER

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,
}