Gatsby production build is different from local build

Viewed 160

I have a weird result in my production build that is not replicated in my local development build. I'm using Gatsby with ReactBootstrap to make Tabs and a Carousel. I've coded the app so that if the screen width is less than 576, the carousel component is loaded, otherwise, the tab component is loaded.

So here's the problem. The first two HTML nodes in service_carousel.js i.e the Container and Carousel tags, are added on page load and become the wrapper for the service_tabs.js code. I don't know why. The services_tabs.js code should be loaded since I'm viewing it from a laptop screen and should only have the nodes specified in services_tabs.js. If I inspect the code and change the device to a phone, the error is fixed and the tags are removed even if I switch back to a large screen. However, if you reload the page the error comes back.

Here's a code sandbox with the full code https://codesandbox.io/s/sad-glade-u8j9g

My code is as follows:

service_tabs.js

import React from 'react';
import styles from './service_tabs.module.scss';

import { TabContent } from '../component_barrel';

import {
  Tab,
  Tabs,
  Row,
  Col,
} from '../../utils/bootstrap_imports_barrel';

import useData from '../../utils/useData';

const tab_data = useData.tab_data;

const ServiceTabs = () => (
  <Row className="justify-content-center p-4">
    <Col lg={10} md={9} className="align-self-center">
      <Tabs justify className={styles.custom_tabs} defaultActiveKey="item 1" id="uncontrolled-tab-example">
        {
          tab_data.map(({ tab_title, title, icon, image, content }, index) => {
            const key = `item ${index + 1}`;

            return (
              <Tab eventKey={key} key={key} title={tab_title}>
                <TabContent
                  icon={icon}
                  image={image}
                  title={title}
                  content={content}
                />
              </Tab>
            )
          })
        }
      </Tabs>
    </Col>
  </Row>
);

export default ServiceTabs;

service_carousel.js

import React from 'react';

import {
  Container,
  Carousel,
} from '../../utils/bootstrap_imports_barrel';

import styles from './service_carousel.module.scss';
import { TabContent } from '../component_barrel';
import useData from '../../utils/useData';

const tab_data = useData.tab_data;

const ServiceCarousel = () => (
  <Container className="p-0" fluid>
    <Carousel className="py-4" controls={false} indicators={false} touch={true}>
      {
        tab_data.map(({ title, icon, image, content }, index) => {
          const key = `item ${index + 1}`;

          return (
            <Carousel.Item key={key} className={styles.carousel_container}>
              <TabContent
                icon={icon}
                image={image}
                title={title}
                content={content}
              />
            </Carousel.Item>
          )
        })
      }
    </Carousel>
  </Container>
);

export default ServiceCarousel;

and the main service.js

import React from 'react';

import {
  ServiceTabs,
  ServiceCarousel
} from './component_barrel'

import { useWindowWidth } from '@react-hook/window-size';

const Service = () => {

  const width = useWindowWidth();
  const componentLoaded = width > 576 ? <ServiceTabs /> : <ServiceCarousel />;

  return (
    <div className="service_container">
      {componentLoaded}
    </div>
  );
};

export default Service;
1 Answers

Since at the initial render point your code is asking for the window's width at const width = useWindowWidth();, your code will only work in the first load since the width of your window is set only one time and it is locked to that value until it re-renders. Your width will only apply to the first render.

To achieve what you are trying to do, you must check for the window availability first to await all your logic before it is set. That will cause a blink of a few milliseconds until the code calculates the window's width and choose what component render but it's the only way to do with any static site generator and window calculations. So, in your service.js:

import React from 'react';

import {
  ServiceTabs,
  ServiceCarousel
} from './component_barrel'

import { useWindowWidth } from '@react-hook/window-size';

const Service = () => {
  let width;
  if (typeof window !== 'undefined') width = useWindowWidth();

  const width = useWindowWidth();
  const componentLoaded = typeof window !== 'undefined' ? width > 576 ? <ServiceTabs /> : <ServiceCarousel /> : null;

  return (
    <div className="service_container">
      {componentLoaded}
    </div>
  );
};

export default Service;

Note the duplicity of typeof window !== 'undefined', should be refactored to avoid repetitions but as an initial approach, it will do the job. In addition, a chained ternary condition is not the best option in terms of readability but for now, it will work.

Basically you are awaiting for the window creation to make your calculations and display a component or another based on the width value, that will be conditioned for the availability of the window.

You can check for further information about the window (and global objects) at Gatsby's documentation.

Related