Creating Responsive Components with GatsbyJS
May 6th 2021
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:
1 import React from 'react'2 import styled from 'styled-components'3 4 const Wrapper = styled.div`5 display: block;6 width: 100%;7 background: #f6f6f6;8 `9 10 const ElementMobile = styled.div`11 display: flex;12 @media screen and (min-width: 1024px) {13 display: none;14 }15 `16 17 const ElementDesktop = styled.div`18 display: none;19 @media screen and (min-width: 1024px) {20 display: block;21 }22 `23 24 export default function MyComponent() {25 return (26 <Wrapper>27 <ElementMobile />28 <ElementDesktop />29 </Wrapper>30 )31 }
To something with much less code as:
1 import React from 'react'2 import styled from 'styled-components'3 import isMobile from 'react-device-detect'4 5 const Wrapper = styled.div`6 display: block;7 width: 100%;8 background: #f6f6f6;9 `10 11 export default function MyComponent() {12 return (13 <Wrapper>14 { isMobile ? <ElementMobile /> : <ElementDesktop />}15 </Wrapper>16 )17 }
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:
1 import React, { useEffect, useState } from 'react'2 3 export default function MobileChecker() {4 5 const [mobileDevice, setMobileDevice] = useState(false)6 7 useEffect(() => {8 const isMobileMatch = window.matchMedia('(max-width: 1024px)').matches; setMobileDevice(isMobileMatch);9 })10 11 return mobileDevice12 }
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
1 import React from 'react'2 import styled from 'styled-components'3 import MobileChecker from './MobileHelper'4 5 const Wrapper = styled.div`6 display: block;7 width: 100%;8 background: #f6f6f6;9 `10 11 export default function MyComponent() {12 const isMobile = MobileChecker()13 return (14 <Wrapper>15 { isMobile ? <ElementMobile /> : <ElementDesktop />}16 </Wrapper>17 )18 }
And voilá! Markup working as expected in all resolutions.