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

未关闭 适合新手
#142 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
84/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
领域
frontend

调研方向

从 packages/redact/src/dom/dom.ts 中 issue 所示的 className 赋值处开始,然后运行提供的 Next.js 复现示例,并在浏览器控制台中检查 class 属性。当 className 为 null 或 undefined 时不留下 class 属性,而空字符串 className 仍会生成 class="",与 React 一致,即为完成。

由索引模型根据 Issue 内容生成。

描述

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;
主要语言
TypeScript
星标
537
派生
22
平均合并
11 小时 28 分钟
30 天内合并 PR
34

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

TanStack/redact 的其他 Issue

查看 TanStack/redact 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。