Skip to content

Commit f4e9710

Browse files
Fix className hydration for <Transition appear> (#2390)
* Fix `className` hydration for `<Transition appear>` * Update changelog
1 parent e814c50 commit f4e9710

File tree

5 files changed

+78
-4
lines changed

5 files changed

+78
-4
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Fix focus styles showing up when using the mouse ([#2347](https://github.com/tailwindlabs/headlessui/pull/2347))
1313
- Fix "Can't perform a React state update on an unmounted component." when using the `Transition` component ([#2374](https://github.com/tailwindlabs/headlessui/pull/2374))
1414
- Add `FocusTrap` event listeners once document has loaded ([#2389](https://github.com/tailwindlabs/headlessui/pull/2389))
15+
- Fix `className` hydration for `<Transition appear>` ([#2390](https://github.com/tailwindlabs/headlessui/pull/2390))
1516

1617
### Added
1718

packages/@headlessui-react/src/components/transitions/transition.ssr.test.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,34 @@ beforeAll(() => {
99

1010
describe('Rendering', () => {
1111
describe('SSR', () => {
12+
it('A transition without appear=true does not insert classes during SSR', async () => {
13+
let result = await renderSSR(
14+
<Transition
15+
as={Fragment}
16+
show={true}
17+
enter="enter"
18+
enterFrom="enter-from"
19+
enterTo="enter-to"
20+
>
21+
<div className="inner"></div>
22+
</Transition>
23+
)
24+
25+
let div = document.querySelector('.inner')
26+
27+
expect(div).not.toBeNull()
28+
expect(div?.className).toBe('inner')
29+
30+
await result.hydrate()
31+
32+
div = document.querySelector('.inner')
33+
34+
expect(div).not.toBeNull()
35+
expect(div?.className).toBe('inner')
36+
})
37+
1238
it('should not overwrite className of children when as=Fragment', async () => {
13-
await renderSSR(
39+
let result = await renderSSR(
1440
<Transition
1541
as={Fragment}
1642
show={true}
@@ -27,6 +53,13 @@ describe('Rendering', () => {
2753

2854
expect(div).not.toBeNull()
2955
expect(div?.className).toBe('inner enter enter-from')
56+
57+
await result.hydrate()
58+
59+
div = document.querySelector('.inner')
60+
61+
expect(div).not.toBeNull()
62+
expect(div?.className).toBe('inner enter enter-from')
3063
})
3164
})
3265
})

packages/@headlessui-react/src/components/transitions/transition.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
436436
let theirProps = rest
437437
let ourProps = { ref: transitionRef }
438438

439-
if (appear && show && env.isServer) {
439+
if (appear && show) {
440440
theirProps = {
441441
...theirProps,
442442
// Already apply the `enter` and `enterFrom` on the server if required

packages/@headlessui-vue/src/components/transitions/transition.ssr.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,41 @@ beforeAll(() => {
1010

1111
describe('Rendering', () => {
1212
describe('SSR', () => {
13+
it('A transition without appear=true does not insert classes during SSR', async () => {
14+
let result = await renderSSR(
15+
defineComponent({
16+
components: Transition,
17+
template: html`
18+
<TransitionRoot
19+
as="template"
20+
:show="true"
21+
enter="enter"
22+
enterFrom="enter-from"
23+
enterTo="enter-to"
24+
>
25+
<div class="inner"></div>
26+
</TransitionRoot>
27+
`,
28+
})
29+
)
30+
31+
let div = document.querySelector('.inner')
32+
33+
expect(div).not.toBeNull()
34+
expect(div?.className).toBe('inner')
35+
36+
// If we don't await then we get the same for SSR and hydration
37+
// but we want to investigate what effects this has on our other transition tests too
38+
await result.hydrate()
39+
40+
div = document.querySelector('.inner')
41+
42+
expect(div).not.toBeNull()
43+
expect(div?.className).toBe('inner enter enter-from')
44+
})
45+
1346
it('should not overwrite className of children when as=Fragment', async () => {
14-
await renderSSR(
47+
let result = await renderSSR(
1548
defineComponent({
1649
components: Transition,
1750
template: html`
@@ -33,6 +66,13 @@ describe('Rendering', () => {
3366

3467
expect(div).not.toBeNull()
3568
expect(div?.className).toBe('inner enter enter-from')
69+
70+
await result.hydrate()
71+
72+
div = document.querySelector('.inner')
73+
74+
expect(div).not.toBeNull()
75+
expect(div?.className).toBe('inner enter enter-from')
3676
})
3777
})
3878
})

packages/@headlessui-vue/src/components/transitions/transition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ export let TransitionChild = defineComponent({
354354
let ourProps = { ref: container }
355355
let theirProps = {
356356
...rest,
357-
...(appear && show && env.isServer
357+
...(appear.value && show.value && env.isServer
358358
? {
359359
// Already apply the `enter` and `enterFrom` on the server if required
360360
class: normalizeClass([

0 commit comments

Comments
 (0)