Skip to content

Commit

Permalink
ppr: prevent CacheNode.loading from being cleared on popstate (vercel…
Browse files Browse the repository at this point in the history
…#68488)

When PPR was enabled, the "restore" action in PPR was causing the
`loading` boundary for the visible `CacheNode` to lose it's `loading`.
This caused the `Suspense` boundary that contained the loading
information to disappear, which in turn caused the tree to remount since
it wraps all the page children.

I can't think of a reason why we'd not want to preserve the existing
`loading` data here, as it's not tied to the prefetch.

closes vercel#68484

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
  • Loading branch information
ztanner and unstubbable committed Aug 3, 2024
1 parent dd40c20 commit 99a9282
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ export function updateCacheNodeOnPopstateRestoration(

prefetchHead: shouldUsePrefetch ? oldCacheNode.prefetchHead : null,
prefetchRsc: shouldUsePrefetch ? oldCacheNode.prefetchRsc : null,
loading: shouldUsePrefetch ? oldCacheNode.loading : null,
loading: oldCacheNode.loading,

// These are the cloned children we computed above
parallelRoutes: newParallelRoutes,
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/app-dir/ppr-history-replace-state/app/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import * as React from 'react'

export function Input() {
const [query, setQuery] = React.useState('')

React.useEffect(() => {
if (!query) {
return
}

window.history.replaceState({ query }, null, `?q=${query}`)
}, [query])

return (
<input
onChange={(event) => setQuery(event.currentTarget.value)}
value={query}
/>
)
}
9 changes: 9 additions & 0 deletions test/e2e/app-dir/ppr-history-replace-state/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react'

export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/ppr-history-replace-state/app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as React from 'react'

export default function Loading() {
return <p>Loading...</p>
}
12 changes: 12 additions & 0 deletions test/e2e/app-dir/ppr-history-replace-state/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react'
import { Input } from './input'

export default function Page() {
return (
<main>
<label>
Enter a query: <Input />
</label>
</main>
)
}
10 changes: 10 additions & 0 deletions test/e2e/app-dir/ppr-history-replace-state/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
ppr: true,
},
}

module.exports = nextConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { nextTestSetup } from 'e2e-utils'

describe('ppr-history-replace-state', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should not remount component', async () => {
const browser = await next.browser('/')
await await browser.elementByCss('input').type('a')
// When the input is remounted, its value is cleared.
expect(await browser.elementByCss('input').getValue()).toBe('a')
})
})

0 comments on commit 99a9282

Please sign in to comment.