Gatsby: How do I make `window.location.href` work???

Created on 6 Nov 2019  路  21Comments  路  Source: gatsbyjs/gatsby

Have the following in a component:

<PromoBanner
  to="/promo"
  style={{
    display:
      window.location.href.indexOf('promo')!== -1 ? 'none'
      :
      window.location.href.indexOf('careers')!== -1 ? 'none'
      :
      window.location.href.indexOf('success')!== -1 ? 'none'
      :
      'grid'
    }}
>
  <PromoCruiseImage src={PromoCruise}/>
  <PromoHeader>Win a free vacation!</PromoHeader>
  <PromoVegasImage src={PromoVegas}/>
</PromoBanner>

which works fine in develop mode.

But when I try to build and/or deploy it, it get this:

failed Building static HTML for pages - 4.244s

 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


  343 |           style={{
  344 |             display:
> 345 |               window.location.href.indexOf('promo')!== -1 ? 'none'
      |               ^
  346 |               :
  347 |               window.location.href.indexOf('careers')!== -1 ? 'none'
  348 |               :


  WebpackError: ReferenceError: window is not defined

  - Layout.js:345 Layout
    src/components/Layout.js:345:15



 ERROR 

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in StoreStateProvider
    in App

and I have no idea how to fix it...馃ズ

deadline is tomorrow as well 馃く

question or discussion

Most helpful comment

i see the destructuring, and it looks nice! I will implement that as well!

Anyhoo, it totally build and is working now at

www.alma.vacations

yay!

All 21 comments

@rchrdnsh when gatsby generates the pages during the build process, it uses node and with that certain apis are not accessible. With that in mind you have to apply some logic here, you need to use something like mentioned here, You need to check the window defined with something like:

const isBrowser = typeof window !== `undefined`

and adjust the style prop accordingly.

hmmmm...i read through that issue and don't understand at all in any what to do...

tried this:

<PromoBanner
  to="/promo"
    style={{
      if (typeof window !== `undefined`) {
        display:
          window.location.href.indexOf('promo')!== -1 ? 'none'
          :
          window.location.href.indexOf('careers')!== -1 ? 'none'
          :
          window.location.href.indexOf('success')!== -1 ? 'none'
          :
          'grid'
      }
    }}
>

but getting 10+ errors and code does not run...

So where would I put that line you wrote to define the browsers window and how would I change the style prop accordingly?

...so lost at the moment 馃様

the code below is not tested and is based on your description.

const fetchLocation = value => {
  return window.location.href.indexOf(value) !== -1;
};
const ParentComponent = () => {
  const isBrowser = typeof window !== `undefined`;
  return (
    <div>
      {isBrowser && (
        <PromoBanner
          to="/promo"
          style={{
            display:
              fetchLocation("promo") !== -1
                ? "none"
                : fetchLocation("careers") !== -1
                ? "none"
                : fetchLocation("success") !== -1
                ? "none"
                : "grid"
          }}
        >
          <PromoCruiseImage src={PromoCruise} />
          <PromoHeader>Win a free vacation!</PromoHeader>
          <PromoVegasImage src={PromoVegas} />
        </PromoBanner>
      )}
    </div>
  );
};

What is happening here is the following:

  • Moved the window.location.href.indexOf to a function to make it more cleaner and pragmatic.
  • Created the variable isBrowser to check to check if the window api is defined and can be accessed.
  • "Short-circuited" the component rendering to try and overcome the issue you're experiencing. More on that here

hmmmm....thank you for the option...I implemented it and nothing has broken but the component is not showing up at all...

...also, after looking at that article you linked do you think it would be better to try and render the whole thing or not? Maybe do an if statement and then render or not render the component tree based on routes? That's all I'm really doing with toggling the display property to none. I want the component to render or not based on the route, just trying to wrap my head around this concept and syntax, etc...

Based on what you said, the way i see it, is that you should only render what you need.

// if the component is in a Page component
export default function SomePage ({ location }) {
  return (
    <PromoBanner
       to="/promo"
       style={{
         display:
           location.href.indexOf('promo')!== -1 ? 'none'
           :
           location.href.indexOf('careers')!== -1 ? 'none'
           :
           location.href.indexOf('success')!== -1 ? 'none'
           :
           'grid'
       }}
    >
      <PromoCruiseImage src={PromoCruise}/>
      <PromoHeader>Win a free vacation!</PromoHeader>
      <PromoVegasImage src={PromoVegas}/>
    </PromoBanner>
  )
}

// if not
// gatsby use @reach/router under the hood, no need npm install
import { Location } from '@reach/router'

export function BannerWithLocation () {
  return (
    <Location>
      {({ location }) => (
        <PromoBanner
          style={{
            display:
              location.href.indexOf('promo') !== -1
                ? 'none'
                : location.href.indexOf('careers') !== -1
                ? 'none'
                : location.href.indexOf('success') !== -1
                ? 'none'
                : 'grid'
          }}
          to='/promo'
        >
          <PromoCruiseImage src={PromoCruise} />
          <PromoHeader>Win a free vacation!</PromoHeader>
          <PromoVegasImage src={PromoVegas} />
        </PromoBanner>
      )}
    </Location>
  )
}

hmmmm...here it is in context...I'm using gatsby-plugin-layout to persist this layout component across all pages, but I want this particular component to only show up on certain pages, but persist between pages that it is required by...I tried adding it to certain pages, but then it would get caught up in the page transition animation, which was not desired...

anyway, here is the total component itself, minus the styled component section:

const Layout = ({ children }) => {

  useEffect(() => {

    if (window.location.href.indexOf('careers')!== -1) {

      if (window.location.href.indexOf('#office-manager') !== -1) {
        let officeManager = document.querySelector('#office-manager').offsetTop
        document.getElementById('main').scrollTop = officeManager
      } else if (window.location.href.indexOf('#administrative-assistant') !== -1) {
        let administrativeAsistant = document.querySelector('#administrative-assistant').offsetTop
        document.getElementById('main').scrollTop = administrativeAsistant
      } else if (window.location.href.indexOf('#vacation-counselor') !== -1) {
        let vacationCounselor = document.querySelector('#vacation-counselor').offsetTop
        document.getElementById('main').scrollTop = vacationCounselor
      } else if (window.location.href.indexOf('#marketing-promoter') !== -1) {
        let marketingPromoter = document.querySelector('#marketing-promoter').offsetTop
        document.getElementById('main').scrollTop = marketingPromoter
      } else {
        document.getElementById('main').scrollTop = 0
      }
    } else {
      document.getElementById('main').scrollTop = 0
    }
  })

  return (
      <App
        animate={{ opacity: 1 }}
        transition={{
        ease: 'easeInOut',
        duration: 1,
        delay: 0 
        }}
      >
        <Menu>
          <Logo/>
          <Nav/>
          <PromoButton/>
        </Menu>
        <Main id='main'>
          {children}
        </Main> 
        <AlmaLogo src={almaLogo} alt="The Alma Logo" />
        <PromoBanner
          to="/promo"
          style={{
            display:
              window.location.href.indexOf('promo')!== -1 ? 'none'
              :
              window.location.href.indexOf('careers')!== -1 ? 'none'
              :
              window.location.href.indexOf('success')!== -1 ? 'none'
              :
              'grid'
          }}
        >
          <PromoCruiseImage src={PromoCruise}/>
          <PromoHeader>Win a free vacation!</PromoHeader>
          <PromoVegasImage src={PromoVegas}/>
        </PromoBanner>
      </App>
  )
}

export default injectIntl(Layout)

Layout component from gatsby-plugin-layout also has props.location, similar to Page component.

const Layout = ({ children, location: { href } }) => {
  useEffect(() => {
    if (href.includes('careers')) {
      if (href.includes('#office-manager')) {
        const officeManager = document.querySelector('#office-manager')
          .offsetTop
        document.getElementById('main').scrollTop = officeManager
      } else if (href.includes('#administrative-assistant')) {
        const administrativeAsistant = document.querySelector(
          '#administrative-assistant'
        ).offsetTop
        document.getElementById('main').scrollTop = administrativeAsistant
      } else if (href.includes('#vacation-counselor')) {
        const vacationCounselor = document.querySelector('#vacation-counselor')
          .offsetTop
        document.getElementById('main').scrollTop = vacationCounselor
      } else if (href.includes('#marketing-promoter')) {
        const marketingPromoter = document.querySelector('#marketing-promoter')
          .offsetTop
        document.getElementById('main').scrollTop = marketingPromoter
      } else {
        document.getElementById('main').scrollTop = 0
      }
    } else {
      document.getElementById('main').scrollTop = 0
    }
  }, [href])

  return (
    <App
      animate={{ opacity: 1 }}
      transition={{
        ease: 'easeInOut',
        duration: 1,
        delay: 0
      }}
    >
      <Menu>
        <Logo />
        <Nav />
        <PromoButton />
      </Menu>
      <Main id='main'>{children}</Main>
      <AlmaLogo alt='The Alma Logo' src={almaLogo} />
      <PromoBanner
        style={{
          display: href.includes('promo')
            ? 'none'
            : href.includes('careers')
            ? 'none'
            : href.includes('success')
            ? 'none'
            : 'grid'
        }}
        to='/promo'
      >
        <PromoCruiseImage src={PromoCruise} />
        <PromoHeader>Win a free vacation!</PromoHeader>
        <PromoVegasImage src={PromoVegas} />
      </PromoBanner>
    </App>
  )
}

export default injectIntl(Layout)

Also, a tip. Use str.includes instead of str.indexOf.

dang, worked perfectly in gatsby develop mode, but this happened when i ran gatsby build

failed Building static HTML for pages - 4.103s

 ERROR #95313 

Building static HTML failed for path "/vi/tina/"

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


  303 |       <PromoBanner
  304 |         style={{
> 305 |           display: href.includes('promo')
      |                         ^
  306 |             ? 'none'
  307 |             : href.includes('careers')
  308 |             ? 'none'


  WebpackError: TypeError: Cannot read property 'includes' of undefined

  - Layout.js:305 Layout
    src/components/Layout.js:305:25



 ERROR 

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in StoreStateProvider
    in App

I just checked and href is not available during SSR. You can use location.pathname and location.hash instead. So for e.g. location.pathname.includes('careers'), location.hash === '#office-manager'.

hmmm... swapped them out, like so:

<PromoBanner
  style={{
    display:
      location.pathname.includes('promo')
      ? 'none'
      : location.pathname.includes('careers')
      ? 'none'
      : location.pathname.includes('success')
      ? 'none'
      : 'grid'
  }}
  to='/promo'
>

...but I still get this error on gatsby build

failed Building static HTML for pages - 4.356s

 ERROR #95312 

"location" is not available during server side rendering.

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


  301 |         style={{
  302 |           display:
> 303 |             location.pathname.includes('promo')
      |             ^
  304 |             ? 'none'
  305 |             : location.pathname.includes('careers')
  306 |             ? 'none'


  WebpackError: ReferenceError: location is not defined

  - Layout.js:303 Layout
    src/components/Layout.js:303:13



 ERROR 

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in StoreStateProvider
    in App

...I'm still pretty lost here 馃槙

do I need to import or define anything?

location is from Layout's props

// here you destructure location props to get pathname and hash
// so you don't have to write location.pathname/location.hash everywhere
const Layout = ({ children, location: { pathname, hash } }) => {
  useEffect(() => {
    if (pathname.includes('careers')) {
      if (hash === '#office-manager') {
        const officeManager = document.querySelector('#office-manager')
          .offsetTop
        document.getElementById('main').scrollTop = officeManager
      } else if (hash === '#administrative-assistant') {
        const administrativeAsistant = document.querySelector(
          '#administrative-assistant'
        ).offsetTop
        document.getElementById('main').scrollTop = administrativeAsistant
      } else if (hash === '#vacation-counselor') {
        const vacationCounselor = document.querySelector('#vacation-counselor')
          .offsetTop
        document.getElementById('main').scrollTop = vacationCounselor
      } else if (hash === '#marketing-promoter') {
        const marketingPromoter = document.querySelector('#marketing-promoter')
          .offsetTop
        document.getElementById('main').scrollTop = marketingPromoter
      } else {
        document.getElementById('main').scrollTop = 0
      }
    } else {
      document.getElementById('main').scrollTop = 0
    }
  }, [hash, pathname]) // also here you need both pathname and hash

  return (
    <App
      animate={{ opacity: 1 }}
      transition={{
        ease: 'easeInOut',
        duration: 1,
        delay: 0
      }}
    >
      <Menu>
        <Logo />
        <Nav />
        <PromoButton />
      </Menu>
      <Main id='main'>{children}</Main>
      <AlmaLogo alt='The Alma Logo' src={almaLogo} />
      <PromoBanner
        style={{
          display: pathname.includes('promo')
            ? 'none'
            : pathname.includes('careers')
            ? 'none'
            : pathname.includes('success')
            ? 'none'
            : 'grid'
        }}
        to='/promo'
      >
        <PromoCruiseImage src={PromoCruise} />
        <PromoHeader>Win a free vacation!</PromoHeader>
        <PromoVegasImage src={PromoVegas} />
      </PromoBanner>
    </App>
  )
}

export default injectIntl(Layout)

wait a minute, i needed to change the thing in the curly braces at the beginning of the component!, ah-ha! So it compiled now in gatsby build!!!

Also note the useEffect dependency array.

holy doodle smacks! I think it works again! Need to upload it to netlify to make sure it builds, but thank you @jonniebigodes and @universse!!! 馃

I will close this out after is successfully deploys :-)

i see the destructuring, and it looks nice! I will implement that as well!

Anyhoo, it totally build and is working now at

www.alma.vacations

yay!

Nice site :)

thanks XD

...also, it seems that the location prop is not really documented for the layout plugin? Am I missing something?

It's mentioned here https://www.gatsbyjs.org/docs/browser-apis/#wrapPageElement which gatsby-plugin-layout use under the hood. I agree that can be improved upon.

i see, it's in the thing that the other thing is wrapped around...thank you :-)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

magicly picture magicly  路  3Comments

jimfilippou picture jimfilippou  路  3Comments

Oppenheimer1 picture Oppenheimer1  路  3Comments

rossPatton picture rossPatton  路  3Comments

ferMartz picture ferMartz  路  3Comments