Creating Responsive Components with GatsbyJS

Working on a project, the time to start enhancing the skeleton, optimize code, clean bits and pieces will come eventually. Happened to me on a specific project using GatsbyJS.

A complex header structure changes for responsiveness were creating some massive markup between using Styled Components and using Media Queries, so decided to have a look around to see what could ReactJS bring to the table (apart of its greatness) and found this react-device-detect package which was more than promising.

You could reduce something from:

1import React from 'react'
2import styled from 'styled-components'
4const Wrapper = styled.div`
5 display: block;
6 width: 100%;
7 background: #f6f6f6;
10const ElementMobile = styled.div`
11 display: flex;
12 @media screen and (min-width: 1024px) {
13 display: none;
14 }
17const ElementDesktop = styled.div`
18 display: none;
19 @media screen and (min-width: 1024px) {
20 display: block;
21 }
24export default function MyComponent() {
25 return (
26 <Wrapper>
27 <ElementMobile />
28 <ElementDesktop />
29 </Wrapper>
30 )

To something with much less code as:

1import React from 'react'
2import styled from 'styled-components'
3import isMobile from 'react-device-detect'
5const Wrapper = styled.div`
6 display: block;
7 width: 100%;
8 background: #f6f6f6;
11export default function MyComponent() {
12 return (
13 <Wrapper>
14 { isMobile ? <ElementMobile /> : <ElementDesktop />}
15 </Wrapper>
16 )

And everything looks good (while on development) but as soon as you have a more complex structure, you will find out that the build process will give you a totally different markup and will mess stuff around very ugly (in my case, all the mobile layout was messed up).

After a couple of hours of debugging and trying to find out why the markup was messed up (at first, nothing points to the react-device-package), I decided to comment out the lines where the package was used and surprisingly everything was working fine.

So, next step... What to do now?

Started looking around, Google, SOF, ReactJS Forums, GatsbyJS Forums, and nothing points towards the issue (I don't believe no one had that issue before), but then I remembered that the reason I was working with GatsbyJS in this project is that: It's a blazingly fast static website generator, with that said, there's no way GatsbyJS can actually render different markups based on a dynamic call when serving the pages.

That made it all clear


Now is time to think of a solution that can be clean and simple at the same time (I didn't want to go back to media queries in every component I didn't want to print in a position I didn't want)

So then, why not create my own "GatsbyJS package" which will listen to DOM changes? That sounds pretty easy, and guess what... it is!

For everyone familiar with the way how GatsbyJS interacts with the DOM and how to manipulate it when the page is already loaded, there's a magical React Hook for that called useEffect

I went down the road creating a custom Hook for it as:

1import React, { useEffect, useState } from 'react'
3export default function MobileChecker() {
5 const [mobileDevice, setMobileDevice] = useState(false)
7 useEffect(() => {
8 const isMobileMatch = window.matchMedia('(max-width: 1024px)').matches; setMobileDevice(isMobileMatch);
9 })
11 return mobileDevice

That will return me a boolean value if the device is a mobile one (we can ignore for now the breakpoint used as 1204px for reference and MVP

What I had to do then, was adding the hook where I need to in the components

1import React from 'react'
2import styled from 'styled-components'
3import MobileChecker from './MobileHelper'
5const Wrapper = styled.div`
6 display: block;
7 width: 100%;
8 background: #f6f6f6;
11export default function MyComponent() {
12 const isMobile = MobileChecker()
13 return (
14 <Wrapper>
15 { isMobile ? <ElementMobile /> : <ElementDesktop />}
16 </Wrapper>
17 )

And voilá! Markup working as expected in all resolutions.