웹 개발을 하다보면 반응형 웹 처리를 항상 마주하게 된다.
처음 React를 공부할 땐 css media
로 반응형 처리를 해주었었는데 아무래도 css에서 일일이 구현하다보니 불편해서(귀찮아서) js에서 반응형 처리를 진행하는 react-responsive를 사용하여 구현해왔다.
최근에 토이프로젝트를 진행하는데 이번에는 반응형이 아닌, 모바일 웹으로만 구현하고 싶어서 PC환경으로 접속시 모바일로 접속해달라는 위와 같은 화면을 띄우고 싶어서 react-responsive를 사용해서 다음과 같이 코드를 작성했다.
const isMobile = useMediaQuery({
query: "(max-width:767px)"
});
// some codes...
<>
{isMobile ?
<PageContainer></PageContainer> :
<MobileWarn/>}
</>
isMobile
은 모바일 환경일 시 (width>767px) true
를 return하고 아닐시에는 false
를 return한다.
내가 생각했던대로라면 모바일로 접속시에는 <PageContainer></PageContainer>
을 rendering 하고 PC로 접속시에는 <MobileWarn/>
rendering 해야한다.
하지만 모바일로 접속했을때 두 컴포넌트가 섞여서 기괴한 모습으로 화면에 출력되는 요상한 상황을 마주하게 되었다.
무엇이 문제인고.. 하니 프로젝트를 구현할 때 Next.js
의 getStaticProps
를 이용하여 SSR으로 구현했는데 이게 react-responsive
와 만나 문제가 발생하게 된 것이었다.
Next의 SSR은 Server에서 React 코드를 html, css, js등으로 변환하여 응답해주는 방식을 띄고 있는데, 최초 응답 시 window 객체가 초기화 되지 않아 window에 대한 정보(width, height..)를 참조하지 않은 채로 데이터가 전송된다.
따라서isMobile
을 이용해서 모바일, PC 두 환경으로 나눠서 구현을 했다고 하더라도, 새로고침시에 나타나는 화면은 window 정보를 모르기 때문에 기본 스타일이 적용되어 나타나게 된다.
해결하는 방법은 SSR을 구현한다고 해도 Client Side에서 컴포넌트들이 마운트 되었을 때 isMobile
변수에 대한 정보를 업데이트 해주면 해결이 가능하다.
// useState를 이용해서 isMobile state 생성
const [isMobile, setIsMobile] = useState(false);
const mobile = useMediaQuery({
query: "(max-width:767px)"
});
useEffect(()=>{ // mobile 쿼리로 인해 값이 바뀔 때 수행
if(mobile) setIsMobile(true);
},[mobile]);
// some codes...
<>
{isMobile ?
<PageContainer></PageContainer> :
<MobileWarn/>}
</>
컴포넌트가 마운트 되고 isMobile
State를 업데이트 시켜서 컴포넌트 re-rendering을 발생시키면 처음에 원했던 대로 구현이 가능하다.
또한 위의 코드는 custom Hook으로 만들어 더 쉽게 사용이 가능하다.
export function useIsMobile () {
const [isMobile, setIsMobile] = useState(false);
const mobile = useMediaQuery({query: "(max-width:767px)"});
useEffect(()=>{
setIsMobile(mobile);
},[mobile]);
return isMobile;
}