react-responsive: [SSR] Rehydrating app contains the wrong classnames.
I use the match render prop callback to set a prop which itself toggles a className. This renders fine on the server but on first load the client does not re-render to correctly display the className. I have even tried to for a state update using setState
in an onChange
callback provided to react-responsive
. For some reason the matches
values is correct when I examine the react tree but the DOM node is missing the correct class name. If I force the breakpoint by shrinking the window and expanding it again everything works correctly. I need that first render to be correct. The react-responsive-redux solution is not good for this use case as it tries to guess the viewport based on the user agent. I don’t need that, I just need the responsive component to re-render in the client. Is there something I am missing to accomplish this? Here is the component I’m trying to get to work:
import React from 'react';
import Responsive from 'react-responsive';
import BorderedRow from '../../../components/BorderedRow';
import styles from './index.scss';
const Posts = ({ posts, className }) => (
<Responsive minWidth={981}>
{match => (
<div>
{posts?.map?.(
({ id }) => (
<BorderedRow stacked={match} key={id} className={styles.post}>
<span>the rest of the content</span>
</BorderedRow>
)
)}
</div>
)}
</Responsive>
);
export default Posts;
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 14
- Comments: 34 (9 by maintainers)
the solution @nhuanhoangduc proposed works quite well. I modified it a bit to use hooks instead of hocs 🙂
@isotopeee I recommend using class component to understand deeply react lifecycle before moving to functional component and hook.
Guys, this is how I solved ssr issue:
Incidentally, looks like Fresnel has a very different approach to solving this problem that might be preferable to some folks.
I’m facing the same issue. It turns out if we have things like this:
and if the client does not match with the server it ends up rendering at the DOM level this:
I tried the redux solution and to force a rerender on the client side, neither of two approaches worked. Looking at https://github.com/facebook/react/issues/10591 I realized that the issue only occurs when the main childrens of
MediaQuery
have similar HTML element but if another element is present in the middle it does not. So, for instance, this ugly workaround solves the issue:Take a look at the
<br/>
I chose that element because if it’s placed a<div/>
instead, or another common HTML tag, for this case the issue will be still present in a worse way:By the moment, until a better solution or a fix comes up, I’ll stick with the ugly workaround 😷
UPDATE: After some playground with the “ugly workaround” this way seems to work better:
For what it’s worth, here’s an example of a wrapper around
useMediaQuery
that fixes things for me. This still causes a flash of incorrect content if the server rendering doesn’t match the browser breakpoint, but at least rehydration will proceed correctly.The hook allows you to specify what screen width the server should assume. It defaults to mobile since the consequences of “guessing” incorrectly are more severe for mobile, ie. if the server assumes desktop, you’ll get a much longer flash of incorrect content at 3G speeds than if it assumes mobile and rehydrates at broadband speeds.
I’d like to pose a question, based on a realization that escaped me for a while… Hoping that it may elucidate the matter for at least some of you that come across this issue. So please bear with me, even if it might question the fundamental approach this library has taken so far…:
Should you actually conditionally render large component trees? It amounts to showing different markup (HTML), which is, in effect: a different page. Even if ever so slightly. Whereas the web was built on the assumption that an url leads to a specific page. Not to a page that is able to morph into a completely different page (even if the ability is not abused to that extent).
Keeping this in mind, then it makes sense that media queries were made for conditionally applying styles to markup. Call it Classic Responsivity. Since the markup is intended to be the same, it went hand-in-hand with classic server-rendering. The way it was done was to render and ship all markup (all breakpoints), but show/hide markup by using CSS in the media queries. If you are willing to do this, and thus ship slightly redundant markup, it can still be done (with the caveat that you’d still get the side-effects from each component being rendered, even the ones not shown).
So, the problem with SSR might indicate that we’re fighting the web (…?) when we’re trying to use media queries to conditionally render markup this way. Call this desire Modern Responsivity. The window.screen or useWindowDimensions API in React Native for Web are actually much more suited for that. (necholas of React Native Web has already lamented how media queries are lacking wrt. responsivity) But these APIs only work client-side, since they inspect the browser window. Thus the pickle we are in with combining SSR + responsivity. Where Fresnel really seems the only viable (yet complex) alternative.
The Fresnel guys actually need some help with a
useMedia
hook if any of you fine folks would like to port youruseMediaQuery
hook there, to join efforts.Some articles that might be relevant / helpful:
Hydrating text content from Server-Side Rendering which explains the React hydration warning you might have stumbled upon:
Text content did not match. Server: "Count: 0" Client: "Count: "
How to combine React Native Web + responsivity + NextJS SSR, to get SEO, gives some ideas on various strategies to employ to get responsivity with SSR.
After tinkering around for a bit trying to solve the SSR issue with nextjs, I came up with following solution which works great on both client and server, just leave it here for people looking for solution in the future.
One nice thing about this approach is that it will re-detect the screen width after resizing, to the best of my knowledge that currently
react-responsive
didn’t support it yet.Hi, if somebody use CSS-in-JS like styled-components and Next.js, I implement a hook made by @jayarnielsen that is
import it in a page
I don´t know if I´m implementing good practices, but it works perfectly. I hope it works for you.
Hi, I use with styled-components, but when i reload, my mobile view, break
Support for SSR should be built-in out of the box in such a library. Currently, in case u want to use it in SSR+CSR enabled framework, like next.js, you basically have to wrap any
useMediaQuery
calls with your own hook/component, that does theisClient
withuseLayoutEffect
thing.Maybe I might make a PR for that in the next few days.
useLayoutEffect is the same as willMount or willReceiveProp, and useEffect is didMount or didUpdate, depends on your requiement
Tested @nhuanhoangduc’s solution and it worked. 🙌
However, I’ve used
useEffect()
instead ofuseLayoutEffect()
and it’s working too.Is there any reason why @nhuanhoangduc used
useLayoutEffect()
specifically?@albertstill this is exactly what we did in our project
Note: the
hasMounted
comes from our HOCOn the server side we detect the device that send the request and we assume some
DEFAULT_SIZE
that we later add to the document for client. On client side we wait with first update to avoid react hydrating warning that content is different than server-side rendered and then we update the media query. Though it is not perfect. It adds 2 additional renders for each media query… I think we will decide in future to do not chech thehasMounted
think and ignore the react hydrating warning (or disable it somehow).This seems like a really common issue for people who use SSR so we might be able to incorporate a fix into react-responsive so people can quit the guessing stuff in the server-side.
Maybe if an
ssr
prop is present, we can do the double-render on the client even though this can cause substantial performance issues.