Gatsby: Mobile page gets served Desktop version incorrectly.

Created on 9 Nov 2018  Â·  7Comments  Â·  Source: gatsbyjs/gatsby

Description

I am having a bizzare problem. I have two types of headers in my site, a desktop version and a mobile version. I am using window.innerwidth to determine which one to display.

Depending on the width, one react component will be loaded over the other.

In the production version of the app, a 'hybrid' version of the header is loaded. Where certain elements of the Desktop Component are loaded, but certain elements of the Mobile Component are loaded.

Steps to reproduce

It is possible to replicate this with the gatsby starter project. Below is a simplified example:

  1. Clone gatsby starter project.
  2. Replace header.js with the following:

import React from 'react'
import { Link } from 'gatsby'`

export const mobileThreshold = 800;

export function isMobile() {
  if (typeof window !== "undefined") {
    if (window.innerWidth < mobileThreshold) {
      return true;
    }
  }
  return false;
}
class Desk extends React.Component {
  render() {
    return (
      <div className="desk-A">
        <div className="desk-B">
          <div className="desk-C">
            <p>This is my desktop header</p>
          </div>
        </div>
      </div>
    )
  }
}

class Mob extends React.Component {
  render() {
    return (
      <div className="mob-A">
        <div className="mob-B">
          <div className="mob-C">
            <p>This is my mobile header</p>
          </div>
        </div>
      </div>
    )
  }
}

export default class Header extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isMobile: isMobile(),
    }
  }

  render() {
    if (this.state.isMobile) {
      console.log('Rendering Mobile Version')
      return <Mob />
    } else {
      console.log('Rendering Desktop Version')
      return <Desk />
    }
  }
}

  1. Run gatsby build , gatsby serve
  2. Open chrome and Toggle the device tool bar so you emulate the mobile.
  3. Navigate to localhost and refresh the page.

Expected result

When I inspect the HTML, I should only see the components for the Mobile header.

Actual result

I see certain elements of the mobile header, and certain elements of the desktop header. It's like the Desktop header gets rendered first, but isn't properly replaced with the mobile header.

See this picture: https://imgur.com/a/Pe8z8ih

Any ideas? In development there is no problem, this only happens in production.

stale? question or discussion

All 7 comments

i've reproduced your steps with a new project and copied your code.
i've ran gatsby develop with device toolbar activated and it's working as it's should. With a screen width of under 800 loads the mobile version and above the desktop version like you described.
Running gatsby build and gatsby serve outputs the same result.
Without any further information i'm more inclined that it's not a gatsby issue but a React issue or the logic you implemented it in your actual project app.

This is probably react hydrate (taking existing DOM from server side rendering and adding event listeners, etc when react js app mounts) limitation (not a bug as it's documented and intended to work that way for performance reason)

React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.

https://reactjs.org/docs/react-dom.html#hydrate

check that link - there are some tips how to treat that

Old issues will be closed after 30 days of inactivity. This issue has been quiet for 20 days and is being marked as stale. Reply here or add the label "not stale" to keep this issue open!

Hey again!

It’s been 30 days since anything happened on this issue, so our friendly neighborhood robot (that’s me!) is going to close it.

Please keep in mind that I’m only a robot, so if I’ve closed this issue in error, I’m HUMAN_EMOTION_SORRY. Please feel free to reopen this issue or create a new one if you need anything else.

Thanks again for being part of the Gatsby community!

I had a similar issue, and in fact was detecting for mobile with react-device-detect for a link in my header. On Android and iOS I wanted to use a certain URL and something different for everything else.

After trying a few different methods of detecting it appeared the static page hydration was causing the same problem. I ended up making the link a component of its own. I would have just changed the existing component but it was functional component that used a hook so changing it to component with a constructor was out of the question.

On my new component I chose one url as the default and then just used react-device-detect within ComponentDidMount() to change the url if it was in fact on iOS or Android.

@adrian271 Had the same issue using react-device-detect and wrapping it in a useEffect with an empty dependency array and storing the value in a useState did the trick for me. Thanks

@adrian271 Had the same issue using react-device-detect and wrapping it in a useEffect with an empty dependency array and storing the value in a useState did the trick for me. Thanks

How exactly? I've went ahead and did this but not much luck on Chrome for iOS which is also impossible to debug on my Mac…

import { isMobileOnly as isMobile } from 'react-device-detect'
...

function IndexPage () {
...

    const [mobile, setMobile] = useState(isMobile)

    useEffect(() => {
      setMobile(isMobile)
    }, [isMobile])

   ...

    return (
        <>
       {!mobile &&
           (<FXLayer spring={spring} />)
       }
        </>
    )
}
Was this page helpful?
0 / 5 - 0 ratings