最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Gatsby: React conditional rendering based on window.innerWidth misbehaving - Stack Overflow

programmeradmin10浏览0评论

Conditional rendering of components based on window.innerWidth seems to not work as intended just in the production build of Gatsby based website.

The hook I am using to check the viewport's width, with the additional check for the window global to avoid Gatsby-node production build errors, is the following:

import { useState, useEffect } from 'react'

const useWindowWidth = () => {
  const windowGlobal = typeof window !== 'undefined'

  if(windowGlobal) {
    const [width, setWidth] = useState(window.innerWidth)

    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth)
      window.addEventListener('resize', handleResize)
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    })

    return width
  }
}

export default useWindowWidth

Then in my actual component I do the following:

IndexPage.Booking = () => {
  const windowWidth = useWindowWidth()

  return (
    <div className="section__booking__wrapper">
      { windowWidth <= mediaQueries.lg && <IndexPage.Cta /> }
      <div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>
        { windowWidth > mediaQueries.lg && <IndexPage.Cta /> }
      </div>
    </div>
  )
}

It works as it should in development but the production build fails to render:

<div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>

When resizing the window below the mediaQueries.lg (1024) it then triggers the actual normal behaviour or conditionally rendering mobile and desktop versions of the component.

To doublecheck if it was because the render triggers on just the resize event (which it doesn't as it works on load in development environment) I also simply, from within the hook console.log() the return value and it gets printed, in production correctly on load.

There are also no errors or warnings in the production or development build whatsoever.

Edit as per @Phillip 's suggestion

const useWindowWidth = () => {
  const isBrowser = typeof window !== 'undefined'
  const [width, setWidth] = useState(isBrowser ? window.innerWidth : 0)

  useEffect(() => {
    if (!isBrowser) return false

    const handleResize = () => setWidth(window.innerWidth)
    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  })

  return width
}

It now works just when you resize it, once, under the mediaQueries.lg threshold and then it works flawlessly across desktop and mobile but not on load.

Conditional rendering of components based on window.innerWidth seems to not work as intended just in the production build of Gatsby based website.

The hook I am using to check the viewport's width, with the additional check for the window global to avoid Gatsby-node production build errors, is the following:

import { useState, useEffect } from 'react'

const useWindowWidth = () => {
  const windowGlobal = typeof window !== 'undefined'

  if(windowGlobal) {
    const [width, setWidth] = useState(window.innerWidth)

    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth)
      window.addEventListener('resize', handleResize)
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    })

    return width
  }
}

export default useWindowWidth

Then in my actual component I do the following:

IndexPage.Booking = () => {
  const windowWidth = useWindowWidth()

  return (
    <div className="section__booking__wrapper">
      { windowWidth <= mediaQueries.lg && <IndexPage.Cta /> }
      <div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>
        { windowWidth > mediaQueries.lg && <IndexPage.Cta /> }
      </div>
    </div>
  )
}

It works as it should in development but the production build fails to render:

<div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>

When resizing the window below the mediaQueries.lg (1024) it then triggers the actual normal behaviour or conditionally rendering mobile and desktop versions of the component.

To doublecheck if it was because the render triggers on just the resize event (which it doesn't as it works on load in development environment) I also simply, from within the hook console.log() the return value and it gets printed, in production correctly on load.

There are also no errors or warnings in the production or development build whatsoever.

Edit as per @Phillip 's suggestion

const useWindowWidth = () => {
  const isBrowser = typeof window !== 'undefined'
  const [width, setWidth] = useState(isBrowser ? window.innerWidth : 0)

  useEffect(() => {
    if (!isBrowser) return false

    const handleResize = () => setWidth(window.innerWidth)
    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  })

  return width
}

It now works just when you resize it, once, under the mediaQueries.lg threshold and then it works flawlessly across desktop and mobile but not on load.

Share Improve this question edited Oct 30, 2019 at 9:59 Mel Macaluso asked Oct 29, 2019 at 13:37 Mel MacalusoMel Macaluso 3,7602 gold badges14 silver badges26 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 11

I had a similar problem to this and haven't found a solution, but a work around. Put the following at the start of your render:

if (typeof window === `undefined`) {
    return(<></>);
}

What I think is happening is that Gatsby is building the page with a style based off the window width (which will be 0 / undefined). Then it's not updating the style in the DOM once the page loads as it thinks it has already performed that action. I think this is a small bug in Gatsby maybe?

Either way, the above renders your component blank during the build, forcing it to fully respect all logic when the page loads. Hopefully that provides a solution albeit not a satisfying/complete explanation :)

I'm guessing it is too late to answer but calling handleResize before adding the event listener should work. Here is a code I used for same purpose:

  useEffect(() => {
  setWidth(window.innerWidth);
  window.addEventListener("resize", () => {
    setWidth(window.innerWidth);
  });
  return () => {
    window.removeEventListener("resize", () => {});
  };
}, []);

Don’t call Hooks inside loops, conditions, or nested functions (from React docs)

React Hooks must run in the exact same order on every render. Move your condition into the useEffect callback:

useEffect(() => {
  if (typeof window === 'undefined') return;

  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  return () => {
    window.removeEventListener('resize', handleResize)
  };
});
发布评论

评论列表(0)

  1. 暂无评论