className={undefined} leaves an empty class="" attribute after client rendering, so [class] selectors match

Ouverte Adaptée aux débutants
#142 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
84/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
next.js, react, typescript
Domaine
frontend

Piste de recherche

Commencez dans packages/redact/src/dom/dom.ts, à l’affectation de className indiquée dans l’issue, puis exécutez la reproduction Next.js fournie et examinez les attributs class dans la console du navigateur. Le travail est terminé lorsque className à null ou undefined ne laisse aucun attribut class, tandis qu’un className constitué d’une chaîne vide produit toujours class="", comme dans React.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

Version: @tanstack/redact 0.2.6 through withRedact, Next.js 16.4.4 (App Router, Turbopack), react/react-dom 19.3.1, Chrome 156, production build. dom.ts is unchanged on main (049de24f).

What happens

When an HTML element is created or updated in the browser with className={undefined}, Redact leaves an empty class="" attribute. React leaves no attribute at all.

The cause is in packages/redact/src/dom/dom.ts:

(el as HTMLElement).className = next == null ? '' : '' + next;

Assigning an empty string to className creates the attribute.

Element React 19.3.1 Redact 0.2.6
className={undefined}, server-rendered and hydrated no attribute no attribute
className={undefined}, server-rendered, then re-rendered no attribute no attribute
className={undefined}, rendered only in the browser no attribute class=""
className changes from 'x' to undefined no attribute class=""
className="" class="" class=""

Why it matters

Selectors that test whether the attribute exists, such as section[class] or :not([class]), now match differently. CSS Modules make this easy to hit, because styles.name is undefined when a class has no rules.

We found it by comparing screenshots of our app against React baselines. An A/A run of React against itself was clean. A global section[class] rule started to apply to one section, which gained 70 px of block padding and a background, and the page grew 140 px taller. Removing the empty attribute in the browser restored the React layout exactly.

Together with #85 and #81, this keeps us from using Redact in production on Next.js.

Reproduction

package.json dependencies: [email protected], [email protected], [email protected], @tanstack/[email protected].

next.config.mjs:

import { withRedact } from '@tanstack/redact/next'

export default withRedact({})

app/layout.js:

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

app/page.js:

'use client'
import { useEffect, useState } from 'react'

export default function Page() {
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])
  return (
    <main>
      <section id="ssr-undefined" className={undefined}>a</section>
      <section id="ssr-empty" className="">b</section>
      <section id="rerender-undefined" className={undefined} data-n={mounted ? 1 : 0}>c</section>
      <section id="toggle-to-undefined" className={mounted ? undefined : 'x'}>d</section>
      {mounted ? <section id="client-undefined" className={undefined}>e</section> : null}
      {mounted ? <section id="client-empty" className="">f</section> : null}
    </main>
  )
}

Run next build && next start, open /, and run this in the console:

Object.fromEntries([...document.querySelectorAll('section')].map((e) => [e.id, e.getAttribute('class')]))

Redact 0.2.6:

{"ssr-undefined":null,"ssr-empty":"","rerender-undefined":null,"toggle-to-undefined":"","client-undefined":"","client-empty":""}

React 19.3.1:

{"ssr-undefined":null,"ssr-empty":"","rerender-undefined":null,"toggle-to-undefined":null,"client-undefined":null,"client-empty":""}

Expected

The same result as React: a null or undefined className leaves no class attribute.

Suggested fix

if (next == null) el.removeAttribute('class');
else (el as HTMLElement).className = '' + next;
Langage dominant
TypeScript
Étoiles
537
Forks
22
Merge moyen
11 h 28 min
PR mergées (30 j)
34

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de TanStack/redact

Toutes les issues de TanStack/redact

Issues similaires

Plus d'issues TypeScript

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.