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

Aberta Para iniciantes
#142 2 comentários 0 reações 0 responsáveis Ver no GitHub

Mantenedores costumam responder em até 1 dia

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
2/5
Tempo estimado
1-3 horas
Facilidade para iniciantes
84/100
Tipo de issue
Bug
Clareza
Claramente especificada
Status de atividade
Ativa
Stack de tecnologia
next.js, react, typescript
Domínio
frontend

Direção de pesquisa

Comece em packages/redact/src/dom/dom.ts, na atribuição de className mostrada na issue; em seguida, execute a reprodução fornecida do Next.js e inspecione os atributos class no console do navegador. O trabalho estará concluído quando um className null ou undefined não deixar nenhum atributo class, enquanto um className com uma string vazia ainda produzir class="", correspondendo ao React.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

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;
Linguagem predominante
TypeScript
Estrelas
537
Forks
22
Merge médio
11h 28min
PRs com merge (30d)
34

Preparar o ambiente

Este projeto não oferece contêiner de desenvolvimento, Dockerfile nem guia de contribuição, então a configuração fica por sua conta: comece pelo README e veja nosso guia da primeira contribuição para os passos gerais.

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de TanStack/redact

Todas as issues de TanStack/redact

Issues semelhantes

Mais issues de TypeScript

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.