Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nicer Formatting of SSR Validation #10085

Closed
sebmarkbage opened this issue Jul 1, 2017 · 38 comments
Closed

Nicer Formatting of SSR Validation #10085

sebmarkbage opened this issue Jul 1, 2017 · 38 comments

Comments

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Jul 1, 2017

The new validation in #10026 only issues a warn for the first difference found in a HTML hydration scenario. Ideally it should instead queue up all the differences and then at the end (commit) issue a single warning with a nicely formatted diff.

  1. Instead of warning add these warn calls to a global buffer (array, map, set, whatever).

  2. Inside prepareForCommit, issue all the currently batched up warnings as a single message.

  3. Format that message in terms of a JSX diff in a nicely formatted way. With only the relevant nodes (parent and child with changes). Irrelevant child content can be replaced with ellipsis. E.g.

...
<div className="unchanged">
- <div className="foo" />
+ <div className="bar">…</div>
+ <span />
</div>
...
<div className="another_unchanged">
- <span />
</div>
...

This strategy won't yield perfect results because if we're asynchronously hydrating, and it gets interrupted by another tree, we'll flush a warning before the actual hydrating particular tree is flushed. So we might show a partial diff in that case. This is probably. It's just a warning.

@akshaynaik404
Copy link

Hi @sebmarkbage, Hi wanted to work on this issue, but I couldn't understand the info you gave above, also I am a beginner in React.

Is this issue suitable for me to is there some docs that I should read to get up to speed with this codebase ...

@sebmarkbage
Copy link
Collaborator Author

A general guide is here: https://facebook.github.io/react/contributing/how-to-contribute.html

For this particular issue you'll probably want to most test it in the SSR fixture. It's a bit messy because you have to build React itself in the root before testing in fixture.

https://github.com/facebook/react/tree/master/fixtures/ssr

This issue is certainly not easy to get right but also doesn't that much existing knowledge of the code base. Just how React works with SSR.

@stepnivlk
Copy link

Hey, @sebmarkbage I'm trying to work on it. I'll probably have some questions, but overall it looks manageable.

@quantizor
Copy link
Contributor

At a minimum, giving the displayName of the component that broke rehydration would be very helpful. I've been seeing a lot of checksum mismatches originating from a library downstream from React (styled-components) and the diff isn't always so helpful because it may just be a generic div and perhaps a CSS class.

@stepnivlk
Copy link

stepnivlk commented Jul 9, 2017

Sry guys I've been busy at work this week. I'm trying to follow @sebmarkbage guide. It's still WIP, but now I have much more time for it.
What I have in one picture:
diff

Two questions (sorry I'm a total newbie to the React codebase):

  1. You talk here about JSX, but in diffHydratedProperties I have access to server rendered dom nodes, it's a bit confusing, can you @sebmarkbage please elaborate a bit about those classNames? I'm not sure if I miss something here.

  2. My way of doing it is to:

  • catch all the diff occurrences, store them as an array of objects containing cloned server dom node and different elements.
  • Iterate over the array, check or clone parent nodes and add/update server and client elements to parent nodes.
  • create one string from each parent.outerHTML and pass it to a warning.
    (I still miss some use cases like one client element/no server element)

Is it fine, stupid, or something else? (Again sorry for a bit retarded question I'm really unsure as I'm not familiar with the codebase yet and want to deliver something useful.)

Cheers.

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

No question is silly. Can you please send a PR and we'll review it? @sebmarkbage is on a vacation now.

@stepnivlk
Copy link

@gaearon Thanks for the reply! OK, I will polish it a bit and open a PR, I'll be glad to get some feedback.

@sebmarkbage
Copy link
Collaborator Author

sebmarkbage commented Jul 13, 2017

You talk here about JSX, but in diffHydratedProperties I have access to server rendered dom nodes...

Yea this is a bit confusing. Let me try to clarify.

The existing diff mechanism will give you propName, serverValue and clientValue passed here.

You'll notice that this is not called "attributeName" and the values passed are of type mixed, not only string. That's because these are props not attributes. React's props differ from attributes in that they can model numbers, booleans, style objects as well as strings. They also have different names than the attributes. E.g. a difference in the class attribute will show the propName as "className" here.

A way to visualize this is server-rendering this: <div className={Date.now()} />

Another one is <input type="checkbox" checked={Math.random() > 0.5} />.

Additionally when you format this text I'd expect the output to be JSX compatible, not HTML compatible. For example, self-closing tags like img have to be encoded as <img />, not <img>. IMO this is better to visualize because it is what the developer is actually coding. The transfer format is mostly opaque unless you happen to try to inspect it.

In fact, we'll likely want to experiment with other transfer formats than HTML and this visualization will transfer nicely to such environments. E.g. imagine React Native server rendering.

Similarly text content's encoding can follow what you'd write in JSX, not what the encoding is in the HTML transfer format. Example: <div>{Date.now()} &gt; 0</div>

@gaearon
Copy link
Collaborator

gaearon commented Jul 31, 2017

We should also probably pass the relevant Fiber's element source to the warnings. This way we can add information about where in the tree we are, similar to other warnings with component stacks.

@sebmarkbage
Copy link
Collaborator Author

@gaearon There isn't just one Fiber that's responsible for the warning. Often it's many which is the point of this batched output. We could possibly include the component stack of the root most parent that failed and join them to create a whole tree view. Seems like it could get very noisy though. Isn't that better left to dev tools if you need a further tree view?

@gaearon
Copy link
Collaborator

gaearon commented Jul 31, 2017

My concern is it’s not always easy to find which <div> the warning is referring to. For example how would you find something like <div className="generated_abc123"> in your code?

@quantizor
Copy link
Contributor

@gaearon see my comment above. Using displayName where possible would dramatically ease finding the fragment of component hierarchy responsible for the issue. E.g

<Foo>
  <div>

Instead of

<div>
  <div>

@sebmarkbage
Copy link
Collaborator Author

It has the parent, which is at least some context.

We only have the component stack from the client side but not the server side.

One issue with showing too much context is that it can be misleading when something diffs because it doesn't line up correctly. E.g. often many different components will have nested divs that we will consider being a match because of the type but they're actually part of a different component. If we show the diff as part of a component tree, it'll look like the bug was within that component but in reality it was actually just the wrong component being rendered at a higher level.

E.g. if App rendered <div><div className="app" /></div> in a branch instead of <Foo />, you'd get an error like this:

<App>
  <Foo>
    <div>
      <Bar>
-       <div className="app" />
+       <div className="bar" />
      </Bar>
    </div>
  </Foo>
</App>

@wenchaojiang
Copy link

wenchaojiang commented Sep 18, 2017

Hi,
I created a pull request #10737 for demo purpose, just want to get feedback to see whether I am on the right track. I have not fixed tests and linting, it is not ready for merging

basically, I managed to get warning message looks like

screen shot 2017-09-18 at 5 31 58 pm

I created a HydrationWarningRenderer module which has the following method

  • setContainer()
    set the container dom element before hydrating. The container is required for
    organizing warnings into a tree structure like a dom tree.

  • registerWarning()
    instead of calling the old warningFor* methods during hydration, we call registerWarning
    method to save diffs
    in a central registry.

  • flushWarnings()
    this method is called in prepareForCommit when hydration is completed.
    There are two steps to print out warnings,
    1. buildWarningTree, 2. warningTreeToText

  • buildWarningTree()
    starting from current container dom element, recursively build a tree, in
    which each node is a pair of {curentDomNode, diffs}

  • warningTreeToText()
    recursively convert diffs Info into warning string and print out the whole
    warning tree as formatted string.

I'd appreciate if anyone can let me know if this approach is worth of keeping working on.

@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2017

Another possible approach (browser-only): #7300

@sachalifs
Copy link

sachalifs commented Oct 4, 2017

It would be nice to see this feature out.

In React v15 the message was more explicit (I could tell which elements were different), but now it just says:

Warning: Expected server HTML to contain a matching <a> in <div>

Also, now I'm having rendering issues due to this warning.

I'm new to React codebase but I will try to give you a hand with this issue too.

@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2017

Sorry, we were focused on getting a release out, and didn't look into existing PRs yet.

@gaearon
Copy link
Collaborator

gaearon commented Jan 5, 2018

The last attempt at this was #10737 but we didn't review it in time and it grew stale. #7300 is a much simpler solution and maybe we should do something like this instead.

If somebody wants to pick this up, let me know. It's not the easiest task so you should be relatively comfortable with working on your own, and discussing the proposed implementation in this issue before jumping to the code. In particular, you'll need to figure out which UX is the most ergonomic for this problem.

sompylasar added a commit to sompylasar/react that referenced this issue Jun 8, 2018
Next, will add a test per every case.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 8, 2018
Example warning:
```
Warning: Expected server HTML to contain a matching <em> in <div>.
 <div className="SSRMismatchTest__wrapper">
   …
   <span className="SSRMismatchTest__2">2</span>
   <span className="SSRMismatchTest__3">3</span>
   <span className="SSRMismatchTest__4">4</span>
   <span className="SSRMismatchTest__5">5</span>
   <span className="SSRMismatchTest__6">6</span>
-  <strong> SSRMismatchTest default text </strong>
+  <em />
   <span className="SSRMismatchTest__7">7</span>
   <span className="SSRMismatchTest__8">8</span>
   <span className="SSRMismatchTest__9">9</span>
   <span className="SSRMismatchTest__10">10</span>
   <span className="SSRMismatchTest__11">11</span>
   …
 </div>

    in em (at SSRMismatchTest.js:224)
    in div (at SSRMismatchTest.js:217)
    in div (at SSRMismatchTest.js:283)
    in SSRMismatchTest (at App.js:14)
    in div (at App.js:11)
    in body (at Chrome.js:17)
    in html (at Chrome.js:9)
    in Chrome (at App.js:10)
    in App (at index.js:8)
```

https://user-images.githubusercontent.com/498274/36351251-d04e8fca-145b-11e8-995d-389e0ae99456.png
sompylasar added a commit to sompylasar/react that referenced this issue Jun 8, 2018
Renders DOM attributes in the tags mentioned in the warnings. Borrows the idea and partially implementation of `getNodeSignature` from @giles-v facebook#12115

Renders DOM diff showing visually the location where the hydration failed. Example warning with a diff:
```
Warning: Expected server HTML to contain a matching <div>{['children ', …]}</div> in <div>nested<!-- --> <p>children <b>text</b></p></div>.
  <div>
    {'nested'}
    {' '}
    <p>children <b>text</b></p>
+   <div>{['children ', …]}</div>
  </div>
    in div (at SSRMismatchTest.js:280)
    in div (at SSRMismatchTest.js:275)
    in div (at SSRMismatchTest.js:308)
    in SSRMismatchTest (at App.js:14)
    in div (at App.js:11)
    in body (at Chrome.js:17)
    in html (at Chrome.js:9)
    in Chrome (at App.js:10)
    in App (at index.js:8)
```

Requires changes to ReactFiberReconciler interface functions that handle hydration errors to distinguish insertion from replacement and show insertion as one added line in the diff; show replacement as one removed, one added line, at correct position among the parentInstance's DOM children:
- add `index` (use `fiber.index`) to point at which child node the insertion or replacement occurs;
- add `isReplaced` to distinguish insertion from replacement.

Extends the proof-of-concept at commit 6c425e7

https://user-images.githubusercontent.com/498274/36652198-11bb46fe-1a62-11e8-9fa2-a612827d1463.gif
sompylasar added a commit to sompylasar/react that referenced this issue Jun 8, 2018
…cebook#10085)

- Change headline to contain only the tag names, not attributes and children.
- Add newlines between headline, diff, and stack.

Besides the above feedback:
- Add comment nodes display in the diff.
- Add weird node handling in the diff.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 8, 2018
…cebook#10085)

- Change headline to contain only the tag names, not attributes and children.
- Add newlines between headline, diff, and stack.

Besides the above feedback:
- Add comment nodes display in the diff.
- Add weird node handling in the diff.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
* Add into existing fixture, not as a new one, to avoid duplicating
server-rendering and hot-reloading code.
* Add the browser-side counterpart for the `url` that was already
passed to `render` at the server but wasn't used. Pass it down to the
`App` component.

Refs facebook#10085
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
…k#10085)

- packages/react-dom: Add getStack (getCurrentFiberStackAddendum) to existing hydrate warnings in ReactDOMFiberComponent
- packages/react-dom: Change two places where getCurrentFiberStackAddendum was called directly to getStack calls to unify its usages in ReactDOMFiberComponent
- packages/react-dom: Add tests for all cases of hydrate mismatch warnings
- fixtures/ssr: Add boolean prop values according to eslint rules of the core codebase in SSRMismatchTest
- fixtures/ssr: Process SSRMismatchTest with prettier
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
Next, will add a test per every case.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
Example warning:
```
Warning: Expected server HTML to contain a matching <em> in <div>.
 <div className="SSRMismatchTest__wrapper">
   …
   <span className="SSRMismatchTest__2">2</span>
   <span className="SSRMismatchTest__3">3</span>
   <span className="SSRMismatchTest__4">4</span>
   <span className="SSRMismatchTest__5">5</span>
   <span className="SSRMismatchTest__6">6</span>
-  <strong> SSRMismatchTest default text </strong>
+  <em />
   <span className="SSRMismatchTest__7">7</span>
   <span className="SSRMismatchTest__8">8</span>
   <span className="SSRMismatchTest__9">9</span>
   <span className="SSRMismatchTest__10">10</span>
   <span className="SSRMismatchTest__11">11</span>
   …
 </div>

    in em (at SSRMismatchTest.js:224)
    in div (at SSRMismatchTest.js:217)
    in div (at SSRMismatchTest.js:283)
    in SSRMismatchTest (at App.js:14)
    in div (at App.js:11)
    in body (at Chrome.js:17)
    in html (at Chrome.js:9)
    in Chrome (at App.js:10)
    in App (at index.js:8)
```

https://user-images.githubusercontent.com/498274/36351251-d04e8fca-145b-11e8-995d-389e0ae99456.png
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
Renders DOM attributes in the tags mentioned in the warnings. Borrows the idea and partially implementation of `getNodeSignature` from @giles-v facebook#12115

Renders DOM diff showing visually the location where the hydration failed. Example warning with a diff:
```
Warning: Expected server HTML to contain a matching <div>{['children ', …]}</div> in <div>nested<!-- --> <p>children <b>text</b></p></div>.
  <div>
    {'nested'}
    {' '}
    <p>children <b>text</b></p>
+   <div>{['children ', …]}</div>
  </div>
    in div (at SSRMismatchTest.js:280)
    in div (at SSRMismatchTest.js:275)
    in div (at SSRMismatchTest.js:308)
    in SSRMismatchTest (at App.js:14)
    in div (at App.js:11)
    in body (at Chrome.js:17)
    in html (at Chrome.js:9)
    in Chrome (at App.js:10)
    in App (at index.js:8)
```

Requires changes to ReactFiberReconciler interface functions that handle hydration errors to distinguish insertion from replacement and show insertion as one added line in the diff; show replacement as one removed, one added line, at correct position among the parentInstance's DOM children:
- add `index` (use `fiber.index`) to point at which child node the insertion or replacement occurs;
- add `isReplaced` to distinguish insertion from replacement.

Extends the proof-of-concept at commit 6c425e7

https://user-images.githubusercontent.com/498274/36652198-11bb46fe-1a62-11e8-9fa2-a612827d1463.gif
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
…cebook#10085)

- Change headline to contain only the tag names, not attributes and children.
- Add newlines between headline, diff, and stack.

Besides the above feedback:
- Add comment nodes display in the diff.
- Add weird node handling in the diff.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
facebook#10085)

When going through the fixtures one by one it's convenient to see the server-side rendered markup in the Chrome DevTools Elements along with the generated warning in Console. The placeholder element adds consistency to the markup: it does not disappear with page reloads, so Chrome DevTools is smart enough to keep it focused.
sompylasar added a commit to sompylasar/react that referenced this issue Jun 9, 2018
facebook#10085)

When going through the fixtures one by one it's convenient to see the server-side rendered markup in the Chrome DevTools Elements along with the generated warning in Console. The placeholder element adds consistency to the markup: it does not disappear with page reloads, so Chrome DevTools is smart enough to keep it focused.
@sompylasar
Copy link
Contributor

The latest screen recording from fixtures/ssr (7.8MB – click to expand)

react-fixtures-ssr-10mb

@sompylasar
Copy link
Contributor

I think the label on this issue should be changed from "good first issue" to "good first issue (taken)" as I've been working on it for quite some time and reached quite some progress in here: #12063

@joseantonjr
Copy link

I can work on this issue, if it's still open. Please let me know

@sompylasar
Copy link
Contributor

@joseantonjr I'm sorry but it's taken and almost done twice: here #12063 (this approach was abandoned after code review from the React Core team) and here #13602 (pending code review from the React Core team).

@rikkit
Copy link

rikkit commented Aug 12, 2019

Excited for this to be merged... SSR setup is finicky enough without having to debug Did not expect server HTML to contain a <div> in <div>. !

@chulanovskyi
Copy link

After so much effort from @sompylasar, we still pocking around with the Warning: Did not expect server HTML to contain a <div> in <div>.. Sad :(

@necolas necolas added the React Core Team Opened by a member of the React Core Team label Jan 8, 2020
@gaearon
Copy link
Collaborator

gaearon commented Feb 26, 2020

We didn't end up doing precisely this. However, we did add component stacks to hydration warnings in React 16.13: https://reactjs.org/blog/2020/03/02/react-v16.13.0.html#component-stacks-in-hydration-warnings

That should get you most of the way there. They might still be occasionally misleading because the problem might be in one of the parent components rendering something unexpected. But they're much better than nothing. I think we can close this as other solutions are too complex.

@threepointone
Copy link
Contributor

I don't think this should have been closed, because one of the goals was to show all warnings that happen on a pass, instead of just the first one that occurs.

If there's interest in this, I'll be happy to be the one who removes the didWarnInvalidHydration flag that enforces this once-only constraint, and fix the ~260 tests that fail on removing it.

@gaearon
Copy link
Collaborator

gaearon commented Mar 7, 2024

We're adding proper diffs in #28512.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

16 participants