sakul
sakul

Reputation: 124

How to check window.innerWidth in React/Gatsby before the website will render?

On the website I create in Gatsby I would like to check the size of the DOM window.innerWidth before it will be first rendered. I would like to use a conditional check of innerWidth to decide how the website should be rendered: as desktop or mobile version. A simple fix for that would be to check the width of the window before creating react component and use the true/false value further in the code. It works in development version but... The case is that in production version when I do gatsby build, I get an error in console:

failed Building static HTML for pages - 2.872s

 ERROR #95312 

"window" is not available during server-side rendering.

See our docs page for more info on this error: https://gatsby.dev/debug-html

> 30 |     const sizeOfWindow = window.innerWidth;

I tried already to use componentWillMount which works fine but it is deprecated and marked as unsafe. If I check the window.innerWidth in componentDidMount doesn't render properly.

My code:

interface IndexPageState {
  isDesktop: boolean;
  windowWidth: number;
}

class IndexPage extends React.Component<any, IndexPageState> {
  constructor(props: any) {
    super(props);

    this.state = {
      windowWidth: 0,
      isDesktop: true,
    };
  }

  componentWillMount(): void {
    this.onResize();
  }

  onResize = () => {
    if (!(this.state.windowWidth >= 769)) {
      this.setState({ isDesktop: false });
    } else {
      this.setState({ isDesktop: true });
    }
  };

  componentDidMount = () => {
    this.setState({ windowWidth: window.innerWidth });
    if (!(this.state.windowWidth >= 769)) {
      this.setState({ isDesktop: false });
    } else {
      this.setState({ isDesktop: true });
    }
    window.addEventListener('resize', this.onResize);
  };

  componentWillUnmount = () => {
    window.removeEventListener('resize', this.onResize);
  };

  render() {
    const { isDesktop, windowWidth } = this.state;

    return (
      <>
        <SEO title="Home" />
        <div className={styles.App}>

Upvotes: 0

Views: 2372

Answers (3)

Ferran Buireu
Ferran Buireu

Reputation: 29335

With Gatsby, you must check for the availability of the browser global variables like document or window, since at the time your code is compiling and asking for those variables, they may not be declared/available yet.

From the Gatsby documentation:

Some of your code references “browser globals” like window or document. If this is your problem you should see an error above like “window is not defined”. To fix this, find the offending code and either a) check before calling the code if window is defined so the code doesn’t run while Gatsby is building (see code sample below) or b) if the code is in the render function of a React.js component, move that code into a componentDidMount lifecycle or into a useEffect hook, which ensures the code doesn’t run unless it’s in the browser

So, every time you check for the window, you must add:

    if(typeof window !== undefined){
     //your stuff
    }

Applied to your code:

  componentDidMount = () => {
    if(typeof window !== undefined){
      this.setState({ windowWidth: window.innerWidth });
      if (!(this.state.windowWidth >= 769)) {
        this.setState({ isDesktop: false });
      } else {
        this.setState({ isDesktop: true });
      }
      window.addEventListener('resize', this.onResize);
    }
  };


  componentWillUnmount = () => {
    if(typeof window !== undefined){
      window.removeEventListener('resize', this.onResize);
    }

   onResize = () => {
    if(typeof window !== undefined){ 
       if (!(this.state.windowWidth >= 769)) {
         this.setState({ isDesktop: false });
       } else {
       this.setState({ isDesktop: true });
       }
     }
   };
};

Of course, the code above should be refactored to avoid the repetition of the condition, by adding the window condition before the event is triggered for example.

Upvotes: 1

Shahnawaz Hossan
Shahnawaz Hossan

Reputation: 2720

Try to execute window.innerWidth after checking the window is defined first in this way.

componentDidMount() {
    if (typeof window !== 'undefined') {
        window.addEventListener('resize', this.onResize)
    }
}
componentWillUnmount() {
    if (typeof window !== 'undefined') {
        window.removeEventListener('resize', this.onResize)
    }
}

Upvotes: 1

Mario Nikolaus
Mario Nikolaus

Reputation: 2406

You can wrap getting the window width in the callback, so add function getWindowWidth where you will return the actual window width.

Upvotes: 1

Related Questions