Skip to content

[react-dom] Normalize attribute whitespace during hydration comparison#35979

Closed
Felipeness wants to merge 1 commit intofacebook:mainfrom
Felipeness:fix/hydration-attr-whitespace-normalization
Closed

[react-dom] Normalize attribute whitespace during hydration comparison#35979
Felipeness wants to merge 1 commit intofacebook:mainfrom
Felipeness:fix/hydration-attr-whitespace-normalization

Conversation

@Felipeness
Copy link

Summary

Related to #35481 — companion to #35978

Browsers normalize tab (\t), newline (\n), and carriage return (\r) characters to spaces when parsing HTML attribute values (HTML spec). React's hydration attribute comparison uses strict string equality, which can produce false mismatch warnings when server and client attribute values differ only by this whitespace normalization.

This change adds a fallback comparison in the hydration path that normalizes \t, \n, and \r to spaces before comparing, matching browser behavior.

What changed

In ReactDOMComponent.js:

  • Added NORMALIZE_ATTRIBUTE_WHITESPACE_REGEX and normalizeAttributeValueForComparison() helper
  • Modified hydrateAttribute(): after strict equality fails, performs a normalized comparison before reporting a mismatch
  • Modified warnForPropDifference(): adds whitespace normalization as an additional comparison step

Context

  • This fix targets the DEV warning path — in production, React does not compare individual attributes during hydration (only text content)
  • Defensive fix — even if source transforms normalize whitespace (see [compiler] Normalize whitespace in JSX string attributes for builtin tags #35978), this ensures React's hydration comparison is resilient to \t\n\r ↔ space differences from any source
  • Preserves existing behavior — strict equality is checked first (fast path), normalization is only a fallback

Relationship to #35978

PR Layer What it fixes
#35978 React Compiler codegen Normalizes whitespace in compiled JSX attribute output
This PR React DOM hydration Prevents false mismatch warnings for whitespace-only differences

Either fix independently resolves the reported issue. Together they provide defense in depth.

How did you test this change?

Added 3 test cases to ReactDOMHydrationDiff-test.js:

Test Scenario
does not warn when attribute values differ only by whitespace normalization className with \n vs space
does not warn for tab and carriage return normalization className with \t and \r\n vs spaces
does not warn for title attribute whitespace normalization title with \n vs space
yarn test packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
# Test Suites: 1 passed, 1 total
# Tests:       40 passed, 40 total (37 existing + 3 new)

Browsers normalize tab, newline, and carriage return characters to spaces
when parsing HTML attribute values. Previously, React's hydration comparison
used strict equality which could produce false mismatch warnings when the
server and client attribute values differ only by this whitespace
normalization.

This change adds a fallback comparison in hydrateAttribute() and
warnForPropDifference() that normalizes \t, \n, and \r to spaces before
comparing, matching browser behavior per the HTML spec.

Fixes facebook#35481
@meta-cla meta-cla bot added the CLA Signed label Mar 8, 2026
@Felipeness
Copy link
Author

Closing this PR after internal review. The premise that browsers normalize \t\n\r to spaces in HTML attribute values is incorrect — the HTML5 tokenizer preserves these characters as-is in the attribute value (the "Anything else" rule in the double-quoted attribute value state).

The actual root cause is in the compiler's codegen layer (Babel serialization artifact), which is addressed in #35978. With that fix in place, server and client attribute values are identical, making this hydration-side normalization unnecessary.

Additionally, this PR had issues:

  • Suppressed potentially real mismatches for attributes like title where \n renders as a visible line break in tooltips
  • Added permanent dev-mode overhead for a problem that should be fixed at the source
  • Test cases simulated the mismatch by passing different strings, not by reproducing the actual compiler bug

@Felipeness Felipeness closed this Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant