react-responsive doesn't work with Server-side rendering

Viewed 988

I am trying to use react-responsive to conditional rendering components base on device width. Everything worked great in client side, but for SSR I followed the documentation to use Context to pass a specific width for initial server render. However, the width that react-responsive received now hard set to the width in Context even if I resize the browser.

Component to define device base on device width:

import { useMediaQuery } from 'react-responsive';

export const Desktop = ({ children }) => {
const isDesktop = useMediaQuery({ minWidth: 801 });
return isDesktop ? children : null;
};

export const Tablet = ({ children }) => {
const isTablet = useMediaQuery({ minWidth: 426, maxWidth: 800 });
return isTablet ? children : null;
};

export const Mobile = ({ children }) => {
const isMobile = useMediaQuery({ maxWidth: 425 });
return isMobile ? children : null;
};

export const MobileTablet = ({ children }) => {
const isMobile = useMediaQuery({ maxWidth: 800 });
return isMobile ? children : null;
};

My use for DeviceIdentifier component:

...
     <Desktop>
        <CategoryTree />
     </Desktop>

...

      <MobileTablet>
        <BurgerMenu
          open={burgerMenuOpen}
          onOpenStateChange={setBurgerMenuOpen}
        />
      </MobileTablet>
...

Context wrapper in _app.js

import { Context as ResponsiveContext } from 'react-responsive';
...
<ResponsiveContext.Provider value={{ width: 1440 }}>
    <Component {...pageProps} />
</ResponsiveContext.Provider>
...

Since I set the width in the context 1440px, my BurgerMenu component is currently never rendered. Anybody have any idea how to make this react-responsive library work in SRR?

2 Answers

Could you check for a window object and use that width and hardcode 1440 for server side:

import { Context as ResponsiveContext } from 'react-responsive';
...
<ResponsiveContext.Provider value={{ width: window.innerWidth || 1440 }}>
    <Component {...pageProps} />
</ResponsiveContext.Provider>
...

From 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.

Related