fix 'loading' segment not being copied into new CacheNode (#66538)
When `router.refresh` or a server action creates a new `CacheNode` tree, we were erroneously not copying over the `loading` segment in the new tree. This would cause the subtree to be remounted as the loading segment switched from having a Suspense boundary to not having one. Fixes #66029 Fixes #66499
This commit is contained in:
parent
58019b8684
commit
855ea3af24
9 changed files with 93 additions and 0 deletions
|
@ -104,8 +104,10 @@ export function refreshReducer(
|
|||
// Handles case where prefetch only returns the router tree patch without rendered components.
|
||||
if (cacheNodeSeedData !== null) {
|
||||
const rsc = cacheNodeSeedData[2]
|
||||
const loading = cacheNodeSeedData[3]
|
||||
cache.rsc = rsc
|
||||
cache.prefetchRsc = null
|
||||
cache.loading = loading
|
||||
fillLazyItemsTillLeafWithHead(
|
||||
cache,
|
||||
// Existing cache is not passed in as `router.refresh()` has to invalidate the entire cache.
|
||||
|
|
|
@ -252,6 +252,7 @@ export function serverActionReducer(
|
|||
const cache: CacheNode = createEmptyCacheNode()
|
||||
cache.rsc = rsc
|
||||
cache.prefetchRsc = null
|
||||
cache.loading = cacheNodeSeedData[3]
|
||||
fillLazyItemsTillLeafWithHead(
|
||||
cache,
|
||||
// Existing cache is not passed in as `router.refresh()` has to invalidate the entire cache.
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { nextTestSetup } from 'e2e-utils'
|
||||
import { retry } from 'next-test-utils'
|
||||
|
||||
describe('actions-revalidate-remount', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
|
||||
it('should not remount the page + loading component when revalidating', async () => {
|
||||
const browser = await next.browser('/test')
|
||||
const initialTime = await browser.elementById('time').text()
|
||||
|
||||
expect(initialTime).toMatch(/Time: \d+/)
|
||||
|
||||
await browser.elementByCss('button').click()
|
||||
|
||||
await retry(async () => {
|
||||
const time = await browser.elementById('time').text()
|
||||
expect(time).toMatch(/Time: \d+/)
|
||||
|
||||
// The time should be updated
|
||||
expect(initialTime).not.toBe(time)
|
||||
|
||||
const logs = (await browser.log()).filter(
|
||||
(log) => log.message === 'Loading Mounted'
|
||||
)
|
||||
|
||||
// There should not be any loading logs
|
||||
expect(logs.length).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,8 @@
|
|||
import { ReactNode } from 'react'
|
||||
export default function Root({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
11
test/e2e/app-dir/actions-revalidate-remount/app/loading.tsx
Normal file
11
test/e2e/app-dir/actions-revalidate-remount/app/loading.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Loading() {
|
||||
useEffect(() => {
|
||||
console.log('Root Loading Mounted')
|
||||
}, [])
|
||||
|
||||
return <p>Root Page Loading</p>
|
||||
}
|
3
test/e2e/app-dir/actions-revalidate-remount/app/page.tsx
Normal file
3
test/e2e/app-dir/actions-revalidate-remount/app/page.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Page() {
|
||||
return <p>hello world</p>
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Loading() {
|
||||
useEffect(() => {
|
||||
console.log('Loading Mounted')
|
||||
}, [])
|
||||
|
||||
return <p>Test Page Loading</p>
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function HomePage() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
return (
|
||||
<div>
|
||||
<p id="time">Time: {Date.now()}</p>
|
||||
<form
|
||||
action={async () => {
|
||||
'use server'
|
||||
revalidatePath('/test')
|
||||
}}
|
||||
>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {}
|
||||
|
||||
module.exports = nextConfig
|
Loading…
Reference in a new issue