Skip to content

Commit a8dc3fe

Browse files
authored
fix(rsc): propagate client reference invalidation to server (#788)
1 parent 1f4b4d9 commit a8dc3fe

File tree

11 files changed

+176
-10
lines changed

11 files changed

+176
-10
lines changed

packages/plugin-rsc/e2e/basic.test.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ function defineTest(f: Fixture) {
415415
await page.getByRole('button', { name: 'client-counter: 0' }).click()
416416
})
417417

418-
test('non-boundary client hmr', async ({ page }) => {
418+
test('non-client-reference client hmr', async ({ page }) => {
419419
await page.goto(f.url())
420420
await waitForHydration(page)
421421

@@ -428,13 +428,55 @@ function defineTest(f: Fixture) {
428428
editor.edit((s) => s.replace('[ok]', '[ok-edit]'))
429429
await expect(locator).toHaveText('test-hmr-client-dep: 1[ok-edit]')
430430

431+
// check next rsc payload includes current client reference and preserves state
432+
await page.locator("a[href='?test-hmr-client-dep-re-render']").click()
433+
await expect(
434+
page.locator("a[href='?test-hmr-client-dep-re-render']"),
435+
).toHaveText('re-render [ok]')
436+
await expect(locator).toHaveText('test-hmr-client-dep: 1[ok-edit]')
437+
431438
// check next ssr is also updated
432-
const res = await page.reload()
439+
const res = await page.request.get(f.url(), {
440+
headers: {
441+
accept: 'text/html',
442+
},
443+
})
433444
expect(await res?.text()).toContain('[ok-edit]')
434445

446+
editor.reset()
447+
await expect(locator).toHaveText('test-hmr-client-dep: 1[ok]')
448+
})
449+
450+
test('non-self-accepting client hmr', async ({ page }) => {
451+
await page.goto(f.url())
435452
await waitForHydration(page)
453+
454+
const locator = page.getByTestId('test-hmr-client-dep2')
455+
await expect(locator).toHaveText('test-hmr-client-dep2: 0[ok]')
456+
await locator.locator('button').click()
457+
await expect(locator).toHaveText('test-hmr-client-dep2: 1[ok]')
458+
459+
const editor = f.createEditor('src/routes/hmr-client-dep2/client-dep.ts')
460+
editor.edit((s) => s.replace('[ok]', '[ok-edit]'))
461+
await expect(locator).toHaveText('test-hmr-client-dep2: 1[ok-edit]')
462+
463+
// check next rsc payload includes an updated client reference and preserves state
464+
await page.locator("a[href='?test-hmr-client-dep2-re-render']").click()
465+
await expect(
466+
page.locator("a[href='?test-hmr-client-dep2-re-render']"),
467+
).toHaveText('re-render [ok]')
468+
await expect(locator).toHaveText('test-hmr-client-dep2: 1[ok-edit]')
469+
470+
// check next ssr is also updated
471+
const res = await page.request.get(f.url(), {
472+
headers: {
473+
accept: 'text/html',
474+
},
475+
})
476+
expect(await res?.text()).toContain('[ok-edit]')
477+
436478
editor.reset()
437-
await expect(locator).toHaveText('test-hmr-client-dep: 0[ok]')
479+
await expect(locator).toHaveText('test-hmr-client-dep2: 1[ok]')
438480
})
439481

440482
test('server hmr', async ({ page }) => {

packages/plugin-rsc/examples/basic/src/routes/hmr-client-dep/client.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33
import React from 'react'
44
import { ClientDep } from './client-dep'
55

6-
export function TestHmrClientDep() {
6+
export function TestHmrClientDep(props: { url: Pick<URL, 'search'> }) {
77
const [count, setCount] = React.useState(0)
88
return (
9-
<div data-testid="test-hmr-client-dep">
10-
<button onClick={() => setCount((c) => c + 1)}>
11-
test-hmr-client-dep: {count}
12-
</button>
13-
<ClientDep />
9+
<div>
10+
<span data-testid="test-hmr-client-dep">
11+
<button onClick={() => setCount((c) => c + 1)}>
12+
test-hmr-client-dep: {count}
13+
</button>
14+
<ClientDep />
15+
</span>{' '}
16+
<a href="?test-hmr-client-dep-re-render">
17+
re-render
18+
{props.url.search.includes('test-hmr-client-dep-re-render')
19+
? ' [ok]'
20+
: ''}
21+
</a>
1422
</div>
1523
)
1624
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function clientDep() {
2+
return '[ok]'
3+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { clientDep } from './client-dep'
5+
6+
export function TestHmrClientDep2(props: { url: Pick<URL, 'search'> }) {
7+
const [count, setCount] = React.useState(0)
8+
return (
9+
<div>
10+
<span data-testid="test-hmr-client-dep2">
11+
<button onClick={() => setCount((c) => c + 1)}>
12+
test-hmr-client-dep2: {count}
13+
</button>
14+
{clientDep()}
15+
</span>{' '}
16+
<a href="?test-hmr-client-dep2-re-render">
17+
re-render
18+
{props.url.search.includes('test-hmr-client-dep2-re-render')
19+
? ' [ok]'
20+
: ''}
21+
</a>
22+
</div>
23+
)
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { clientDep } from './client-dep'
5+
import { ClientDepComp } from './client-dep-comp'
6+
7+
export function TestHmrClientDepA() {
8+
const [count, setCount] = React.useState(0)
9+
return (
10+
<>
11+
<span data-testid="test-hmr-client-dep3">
12+
<button onClick={() => setCount((c) => c + 1)}>
13+
test-hmr-client-dep3: {count}
14+
</button>
15+
{clientDep()}
16+
<ClientDepComp />
17+
</span>
18+
</>
19+
)
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use client'
2+
3+
import { TestHmrClientDepA } from './client-a'
4+
5+
export function TestHmrClientDepB() {
6+
return <TestHmrClientDepA />
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function ClientDepComp() {
2+
return '[ok]'
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function clientDep() {
2+
return '[ok]'
3+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { TestHmrClientDepA } from './client-a'
2+
import { TestHmrClientDepB } from './client-b'
3+
4+
// example to demonstrate a folowing behavior
5+
// https://github.com/vitejs/vite-plugin-react/pull/788#issuecomment-3227656612
6+
/*
7+
server server
8+
| |
9+
v v
10+
client-a client-a?t=xx <-- client-b
11+
| |
12+
v v
13+
client-dep-comp?t=xx
14+
*/
15+
16+
export function TestHmrClientDep3() {
17+
return (
18+
<div>
19+
<TestHmrClientDepA />
20+
<TestHmrClientDepB />
21+
</div>
22+
)
23+
}

packages/plugin-rsc/examples/basic/src/routes/root.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import { TestHmrSwitchClient } from './hmr-switch/client'
4141
import { TestTreeShakeServer } from './tree-shake/server'
4242
import { TestClientChunkServer } from './chunk/server'
4343
import { TestTailwind } from './tailwind'
44+
import { TestHmrClientDep2 } from './hmr-client-dep2/client'
45+
import { TestHmrClientDep3 } from './hmr-client-dep3/server'
4446

4547
export function Root(props: { url: URL }) {
4648
return (
@@ -63,7 +65,9 @@ export function Root(props: { url: URL }) {
6365
<TestTailwind />
6466
<TestDepCssInServer />
6567
<TestHydrationMismatch url={props.url} />
66-
<TestHmrClientDep />
68+
<TestHmrClientDep url={{ search: props.url.search }} />
69+
<TestHmrClientDep2 url={{ search: props.url.search }} />
70+
<TestHmrClientDep3 />
6771
<TestHmrSharedServer />
6872
<TestHmrSharedClient />
6973
<TestHmrSharedAtomic />

0 commit comments

Comments
 (0)