Open fabb opened 2 years ago
@sebmarkbage @acdlite Any idea here?
Currently the only mitigation against this problem is to wrap the component causing a mismatch with a <Suspense>
boundary and use unstable_avoidThisFallback
to stop it from actually "Suspending" during server rendering (ie. treating it as a fragment for initial rendering but giving it a suspense boundary on the client). The result would be that on the client, on mismatch, we would render the component again but as a separate client render after the mismatch is detected. If data / requests are cached outside of React then it wouldn't be that bad, but the additional render is a bit meh. Our current thinking is that mismatches shouldn't happen but I think in practice especially when using third party libraries they are very hard to avoid (at least now, until library authors realize the damage they cause with this kind of behavior).
I think the approach of "gracefulHydrationErrors" around a specific component could make sense imo.
I don't understand the premise of the question. I'd like to get more clarity on what's being asked before we discuss any potential APIs.
The canonical solution to a difference in client/server output is indeed useEffect
that switches state on the client. Yes, it would "flash" from the server output — but this seems inherent to what you're trying to do? If initial render is HTML and it takes a while for JS to load, there's no escaping that there will be server output shown first, and after a while the client kicks in. There's no way not to have the "flash" in principle.
So I don't understand what is being asked and why useEffect
is not enough here. Maybe you can describe the current behavior and the ideal behavior from the user perspective? What does the user see before and after JS loads/runs? And how does the desired solution differ from user's perspective from the useEffect
solution?
getViewStateFromLocalStorage is reading mutable state during render which has other issues and considerations.
useSyncExternalStore is the API suggested to deal with external mutable state.
That API has an SSR option which is where you’re supposed to return the value that should be used on the server and the value that should be used during hydration. You can always return a default value for SSR there.
The non-hydration case doesn’t use the SSR path and so it reads the current value.
It doesn’t seem documented yet but it’s there:
Ah I think I understand the question after re-reading a few times.. So it's about how to write a component that would "skip" the unnecessary effect for next client-only mounts (since those don't need to "wait" for anything).
Ah I think I understand the question after re-reading a few times.. So it's about how to write a component that would "skip" the unnecessary effect for next client-only mounts (since those don't need to "wait" for anything).
Exactly! Sorry if I didn‘t describe it clearly enough.
Ah I think I understand the question after re-reading a few times.. So it's about how to write a component that would "skip" the unnecessary effect for next client-only mounts (since those don't need to "wait" for anything).
@gaearon do you have a suggestion on how to implement this best, or is it currently not possible with react?
I think useSyncExternalStore should work for this?
You mean using getServerSnapshot
?
Hi, is it the correct way of detecting client rendering?
const subscribe = () => () => {};
const getClientSnapshot = () => true;
const getServerSnapshot = () => false;
export const useIsClient = () =>
useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
This question is about hydration errors and workarounds that are future-proof for React 18 partial hydration and concurrent mode.
React hydration rules say that the server rendered html needs to match the client rendered dom that is rendered during the initial render in
hydrate()
. Mismatches (=slight differences in dom output) can cause all kinds of weird behavior because React's virtual dom does not match the real dom. Such mismatches can happen when rendering based on information that is only available on the client side, but not on the server side, e.g. conditional rendering based ontypeof window !== 'undefined'
, or rendering based on data fromlocalStorage
.So this component will cause a hydration error if it is contained in the initial sever-rendered html (case 1), but it would not cause a hydration error if it only appeared later after hydration (case 2):
A common workaround is to use
useEffect
to apply client data only after hydration:This workaround comes with a downside: for case 2 where the component only appears later after hydration, it would still flash from showing
"A"
first, and then the view state fromlocalStorage
. To make the component directly show the view state fromlocalStorage
, the code inMyComponent1
would need to be used, but then the component can not be used in initial server renderings. So the component itself needs knowledge in which contexts it will be used, which is not ideal for modularity.I currently know of no way to make the component work for both case 1 and case 2 and show the view state from
localStorage
directly in the initial render for case 2 without giving the component knowledge of its outer context.I see 2 different theoretical approaches to solve this issue which are not yet possible in React AFAIK:
hydrate()
:Is there already a way to solve this issue properly with available apis? The solution also needs to work with React 18 partial hydration and concurrent mode.