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

Abierto Apto para principiantes
#142 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
84/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
next.js, react, typescript
Área
frontend

Línea de trabajo

Empieza en packages/redact/src/dom/dom.ts, en la asignación de className que se muestra en el issue; después, ejecuta la reproducción de Next.js proporcionada e inspecciona los atributos class en la consola del navegador. El trabajo está terminado cuando un className null o undefined no deja ningún atributo class, mientras que un className con una cadena vacía sigue generando class="", como hace React.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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;
Lenguaje dominante
TypeScript
Estrellas
537
Forks
22
Merge medio
11 h 28 min
PR fusionados (30 d)
34

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de TanStack/redact

Todos los issues de TanStack/redact

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.