简体   繁体   English

Gatsby:基于 window.innerWidth 行为不端的 React 条件渲染

[英]Gatsby: React conditional rendering based on window.innerWidth misbehaving

Conditional rendering of components based on window.innerWidth seems to not work as intended just in the production build of Gatsby based website.基于window.innerWidth的组件的条件渲染似乎无法在基于 Gatsby 的网站的生产版本中按预期工作。

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:我用来检查视口宽度的钩子,以及对 window 全局的额外检查以避免 Gatsby 节点生产构建错误,如下所示:

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:它在development中可以正常工作,但生产构建无法呈现:

<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.当在 mediaQueries.lg (1024) 下调整 window 的大小时,它会触发实际的正常行为或有条件地呈现组件的移动和桌面版本。

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.要仔细检查是否是因为渲染仅在resize事件上触发(它不会在development环境中加载),我也简单地从钩子console.log()中返回值并打印出来,在生产中正确加载。

There are also no errors or warnings in the production or development build whatsoever.生产development版本中也没有任何错误或警告。

Edit as per @Phillip 's suggestion根据@Phillip 的建议进行编辑

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.它现在只在你调整它的大小时工作,一次,在 mediaQueries.lg 阈值下,然后它可以在桌面和移动设备上完美地工作,但不能在加载时工作。

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).我认为正在发生的是 Gatsby 正在使用基于 window 宽度(将为 0 / 未定义)的样式构建页面。 Then it's not updating the style in the DOM once the page loads as it thinks it has already performed that action.然后它不会在页面加载后更新 DOM 中的样式,因为它认为它已经执行了该操作。 I think this is a small bug in Gatsby maybe?我认为这可能是 Gatsby 中的一个小错误?

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.我猜现在回答为时已晚,但在添加事件侦听器之前调用 handleResize 应该可以工作。 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 )不要在循环、条件或嵌套函数中调用 Hooks来自 React 文档

React Hooks must run in the exact same order on every render. React Hooks必须每次渲染时以完全相同的顺序运行。 Move your condition into the useEffect callback:将您的条件移动到useEffect回调中:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM