Skip to content

Commit

Permalink
Add Diffs to Hydration Warnings (#28512)
Browse files Browse the repository at this point in the history
Stacked on #28502.

This builds on the mechanism in #28502 by adding a diff of everything
we've collected so far to the thrown error or logged error.

This isn't actually a longest common subsequence diff. This means that
there are certain cases that can appear confusing such as a node being
added/removed when it really would've appeared later in the list. In
fact once a node mismatches, we abort rendering so we don't have the
context of what would've been rendered. It's not quite right to use the
result of the recovery render because it can use client-only code paths
using useSyncExternalStore which would yield false differences. That's
why diffing the HTML isn't quite right.

I also present abstract components in the stack, these are presented
with the client props and no diff since we don't have the props that
were on the server. The lack of difference might be confusing but it's
useful for context.

The main thing that's data new here is that we're adding some siblings
and props for context.

Examples in the [snapshot
commit](e14532f).
  • Loading branch information
sebmarkbage committed Mar 27, 2024
1 parent f7aa5e0 commit 2ec2aae
Show file tree
Hide file tree
Showing 4 changed files with 926 additions and 33 deletions.
16 changes: 14 additions & 2 deletions packages/react-dom-bindings/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ function warnForExtraAttributes(
) {
if (__DEV__) {
attributeNames.forEach(function (attributeName) {
serverDifferences[attributeName] =
serverDifferences[getPropNameFromAttributeName(attributeName)] =
attributeName === 'style'
? getStylesObjectFromElement(domElement)
: domElement.getAttribute(attributeName);
Expand Down Expand Up @@ -1829,12 +1829,24 @@ function getPossibleStandardName(propName: string): string | null {
return null;
}

function getPropNameFromAttributeName(attrName: string): string {
switch (attrName) {
case 'class':
return 'className';
case 'for':
return 'htmlFor';
// TODO: The rest of the aliases.
default:
return attrName;
}
}

export function getPropsFromElement(domElement: Element): Object {
const serverDifferences: {[propName: string]: mixed} = {};
const attributes = domElement.attributes;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
serverDifferences[attr.name] =
serverDifferences[getPropNameFromAttributeName(attr.name)] =
attr.name.toLowerCase() === 'style'
? getStylesObjectFromElement(domElement)
: attr.value;
Expand Down
Loading

0 comments on commit 2ec2aae

Please sign in to comment.