yocontra / react-responsive

CSS media queries in react - for responsive design, and more.
https://contra.io/react-responsive
MIT License
7.04k stars 298 forks source link

Context for server-side-rendering hard set the width #257

Closed lenghia241 closed 1 year ago

lenghia241 commented 4 years ago

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 even if resize the browser. Anybody have any idea how to make this work both in SSR and client side?

yocontra commented 4 years ago

Can you check for document and not provide the context when it exists?

lenghia241 commented 4 years ago
              {typeof window !== 'undefined' ? (
                <Component {...pageProps} />
              ) : (
                <ResponsiveContext.Provider value={{ width: deviceWidth }}>
                  <Component {...pageProps} />
                </ResponsiveContext.Provider>
              )}

Seems like this work.

lenghia241 commented 4 years ago

@contra This solution might not be optimal because it causes a complete re-render instead of a proper hydrate then it affects poorly on the whole application performance. Is there a support for hydrating on the client side without any side effects?

yocontra commented 4 years ago

@lenghia241 You will always have to do a re-render because there is no possible way for the server to guess the width/height/etc. of the browser before sending the content. If you don't want to do any re-render with SSR you should move the media query to pure CSS if possible.

pwlmaciejewski commented 4 years ago

I had a similar issue and double client render works fine for me:

function MyApp() {
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    setInitialized(true);
  }, []);

  const app = (<div>My app</div>);

  return initialized ? (
    app
  ) : (
    <ResponsiveContext.Provider value={{ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }}>
      {app}
    </ResponsiveContext.Provider>
  );
}

On the server, it will render the app with the default dimensions DEFAULT_WIDTH x DEFAULT_HEIGHT. On the client it will: 1) render initially with the default dimensions to hydrate properly 2) re-render with the correct browser dimensions

@lenghia241 Let me know if that works for you.

yocontra commented 1 year ago

Added some more info to the docs RE: SSR specifically using next.js, closing this now.