Reputation: 483
Im currently using 'react-responsive' to toggle on or off some components depending if the app is viewed on desktop or mobile.
Simplified example:
import { useMediaQuery } from 'react-responsive'
export default function MyComponent(props) {
const isMobile = useMediaQuery({ query: `(max-width: 480px)` })
return (
<Wrapper>
{ isMobile ? <ComponentMobile /> : <ComponentDesktop /> }
</Wrapper>
)
}
When I navigate from page to page within the app this works perfectly, but when I open the website for the first time the components are all wrong (still rendering ComponentDesktop) even if I'm on mobile.
My guess is that the component is being rendered serverside and when it comes to the frontend it doesn't update.
What is the correct method for doing this?
Many thanks!
Upvotes: 1
Views: 1343
Reputation: 3645
I had to create a useEffect()
wrapper around useMediaQuery()
hook to solve issues with hydration...
import { useEffect, useState } from 'react';
import { useMediaQuery } from 'react-responsive';
export const IS_SERVER = typeof window === 'undefined';
export const MOBILE_SCREEN_MAX_WIDTH = 640;
export const SERVER_SIDE_IS_MOBILE_VALUE = true; // true - for mobile, false - for desktop
/**
* Hook to detect onMobile vs. onDesktop using Media Query
* @returns {boolean} true when on onMobile, false when on onDesktop
*/
function useOnMobileByMediaQuery() {
const onMobile = useMediaQuery({ maxWidth: MOBILE_SCREEN_MAX_WIDTH });
return onMobile;
}
/**
* Hook to detect onMobile vs. onDesktop with Next.js workaround
* @returns {boolean} true when on onMobile, false when on onDesktop
*/
function useOnMobileForNextJs() {
const onMobile = useOnMobileByMediaQuery();
const [onMobileDelayed, setOnMobileDelayed] = useState(SERVER_SIDE_IS_MOBILE_VALUE);
useEffect(() => {
setOnMobileDelayed(onMobile); // Next.js don't allow to use useOnMobileXxx() directly, so we need to use this workaround
}, [onMobile]);
return onMobileDelayed;
}
/**
* We need a "smart export wrappers", because we can not use hooks on the server side
*/
export const useOnMobile = IS_SERVER ? () => SERVER_SIDE_IS_MOBILE_VALUE : useOnMobileForNextJs;
Public Gist is here https://gist.github.com/karpolan/9b9b2c781480cfc9a4bd2bfe62f332f3
Upvotes: 0
Reputation: 709
As per reactjs docs To fix this, either move that logic to useEffect (if it isn’t necessary for the first render), or delay showing that component until after the client renders (if the HTML looks broken until useLayoutEffect runs).
To exclude a component that needs layout effects from the server-rendered HTML, render it conditionally with showChild && and defer showing it with useEffect(() => { setShowChild(true); }, []). This way, the UI doesn’t appear broken before hydration.
Upvotes: 1
Reputation: 399
Ran into a similar problem recently, ended up showing or hiding the components using CSS. Material UI has a Hidden component that allows you to do this easily using CSS, but there are certainly other solutions.
See this video from Vercel for multiple possible implementations: https://www.youtube.com/watch?v=K7g8X_VRDy8
Checking the user agent is also possible, but I personally like the CSS idea simply because it shows or hides components based on the width of the screen automatically, but it is certainly not perfect.
Upvotes: 1