Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Bug: Dev hydration false-positive for equivalent class token sets #34939

Open
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug
@teamleaderleo

Description

Proposed fix in PR: #34940

React version: 19.x (also reproducible on current main)

Steps To Reproduce

  1. Server-render markup where class values are token-equivalent but differ by whitespace/order/duplicates.
  2. Hydrate on the client with the same tokens in a different order/spacing.

Minimal reproduction:

import * as React from 'react';
import * as ReactDOMClient from 'react-dom/client';
import * as ReactDOMServer from 'react-dom/server';
const container = document.createElement('div');
container.innerHTML = ReactDOMServer.renderToString(
<main className="foo bar bar" />
);
ReactDOMClient.hydrateRoot(
container,
<main className="bar foo" />,
{ onRecoverableError: () => {} } // surfaces the warning in DEV
);

CRLF variant:

container.innerHTML = ReactDOMServer.renderToString(
<main className="flex\r\nitems-center" />
);
ReactDOMClient.hydrateRoot(container, <main className="flex items-center" />);
CRLF hydration

Link to code example

Open the Console to see the hydration warning (DEV build).

The current behavior

In DEV, React logs a hydration attribute mismatch warning when the server and client class strings differ only by whitespace, token order, or duplicate tokens, even though they represent the same token set.

The expected behavior

No warning. Per HTML, class is a set of space-separated tokens, so differences in whitespace, order, or duplicate tokens should be considered equivalent during hydration diffing in DEV.

Specs:

Why this matters:

This false positive can be commonly triggered by:

  • Windows line endings (CRLF) in multiline template literals
  • Class-merging utilities (clsx, cn) with different evaluation order
  • Different bundler/minifier behavior between server and client

Proposed fix:

Normalize both values before comparison (DEV-only):

function normalizeClassForHydration(markup: mixed): string {
const s = normalizeMarkupForTextOrAttribute(markup);
const tokens = s.trim().split(HTML_SPACE_CLASS_SEPARATOR).filter(Boolean);
const unique = Array.from(new Set(tokens));
unique.sort();
return unique.join(' ');
}

This proposal:

  • Handles whitespace per HTML spec (ASCII whitespace characters)
  • Deduplicates tokens (matches DOMTokenList behavior)
  • Sorts for order-independence (classes are unordered)
  • Uses default lexicographic sort (UTF-16 code unit order), which is deterministic and locale-independent
  • Reuses existing normalizeMarkupForTextOrAttribute for consistency

What wouldn’t be suppressed

  • Missing/extra class tokens
  • Non-ASCII whitespace that changes tokenization (e.g., NBSP)
  • Any difference that affects layout/behavior

Notes: This is DEV-only (warning path). Production behavior is unaffected. A small DEV change to compare normalized token sets would avoid this false positive.

This should preserve React's ability to catch genuine mismatches while eliminating false positives for token-equivalent classes.

Precedent:

React already normalizes attributes during hydration for HTML parser behavior (see 44c32fc - CRLF normalization from 2017). This extends that pattern to handle class's token-set semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

        AltStyle によって変換されたページ (->オリジナル) /