Open theKashey opened 2 years ago
Just to set expectations -- We're heads down preparing for the conf so the response may be delayed but thanks for a good writeup.
Which conf is that one @gaearon?
Which conf is that one @gaearon?
@maraisr React Conf 2021
I think I'm running into the same thing here.
On the server side, I need to wrap my app with a shell like this:
<html>
<body>
<div id="root"></div> <!-- this is what gets hydrated on the client side -->
</body>
</html>
This works great until I start using useId()
. Now I get hydration mismatch errors because the ids don't match. I can "fix" it using Noop components like they did with Next.js, but that's super brittle and confusing.
Is this considered a bug or expected behavior? Is there a better workaround that I'm just not aware of?
useId
is based on Tree structure. There is no other workaround.
Thanks for the confirmation @theKashey. Looking at the SSR demo it seems like the expected pattern is to hydrate the entire document on the client side and not just the app root. I haven't tried this myself, but maybe it could work?
That said, it looks like they are wrapping the app root in a data provider on the server only:
So, I'm guessing they will have hydration mismatch errors if anything in <App />
uses useId
. I haven't tested it myself though.
It's actually pretty simple thing - useId
is affected only by sibling presence in the parent components.
Let's assume we have App
using useId
<App/>
// is the same as
<Provider>
<AnotherProvider>
<App/>
</AnotherProvider>
</Provider>
because there is no "difference in sibling" in parents. And the change in parents does not matter.
Given another example
<>
<Stuff/>
<App/>
</>
// is the same as
<>
<Stuff>
<SomeOtherStuff/>
</Stuff>
<App/>
</>
Because there is change, but not among "siblings in parents" - change is in "another tree branch", which will have issues using useId
That said, it looks like they are wrapping the app root in a data provider on the server only
I'd say that's a mistake in the example.
On the server side, I need to wrap my app with a shell like this
The canonical solutions are either to add this wrapper to the client (and hydrate the whole document) or not use React to generate this wrapper on the server either.
@theKashey thanks for the clarification. I didn't realize that it had to do with the siblings.
@gaearon thanks for information on the suggested solutions. I'll have to rethink quite a bit about how I approach SSR, but at least now I know what I should be aiming for.
Ah - this is exactly what I was battling yesterday! I think this thread explains the behavior I was seeing and maybe there's room for a small docs update or clarity. I was confused by what I read in the docs versus the behavior I was seeing. The current docs don't mention how useId
generates it's value, but the beta docs mention:
Inside React, useId is generated from the “parent path” of the calling component. This is why, if the client and the server tree are the same, the “parent path” will match up regardless of rendering order.
If it really does come down to both the parents and their siblings then I would suggest updating the docs to be a bit clearer. It currently reads to me like only the path to the current component matters, not the siblings along that path.
Here's a simplified example showing the parent sibling impact on useId
: https://stackblitz.com/edit/node-afgxnd
Origins
Historically SSR was requiring some extra components to create a special "server" environment. Usually the ServerApplication is expected to be wrapped with different
collectors
andproviders
in order to power code splitting, style extraction, media queries, and many other things, some part of which don't have to be used on the client side, or even cannot exists at all.ClientSide in turn, might contain some elements not required for the Server
The problem
According to my experiments for the proper use of
useId
one does not need ideally matching component trees - any number of "wrappers" are allowed, and only having "more than one child" is breaking id generation, however it does not cause any hydration id and cannot be detected without a context-aware test.The question
What level of similarity is really required? What actually matters - the path(so internals of siblings do not matter), or everything "before this point"(probably not due to Selective Hydration)?
How one can understand are component trees are similar enough, or one should not try to do that, comparing the expected behavior (matching Ids) without relying on implementation details of
useId
(currently one has to)