Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensure prefetch entry is not mutated #69948

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,23 +172,26 @@ export function navigateReducer(
let scrollableSegments: FlightSegmentPath[] = []
for (const normalizedFlightData of flightData) {
const {
tree: treePatch,
pathToSegment: flightSegmentPath,
seedData,
head,
isRootRender,
} = normalizedFlightData
let treePatch = normalizedFlightData.tree

// TODO-APP: remove ''
const flightSegmentPathWithLeadingEmpty = ['', ...flightSegmentPath]

// Segments are keyed by searchParams (e.g. __PAGE__?{"foo":"bar"}), so if we returned an aliased entry,
// we need to ensure the correct searchParams are provided in the updated FlightRouterState tree.
// Segments are keyed by searchParams (e.g. __PAGE__?{"foo":"bar"}). We might return a less specific, param-less entry,
// so we ensure that the final tree contains the correct searchParams (reflected in the URL) are provided in the updated FlightRouterState tree.
if (prefetchValues.aliased) {
treePatch[0] = addSearchParamsIfPageSegment(
treePatch[0],
const [segment, ...rest] = treePatch
const finalSegment = addSearchParamsIfPageSegment(
segment,
Object.fromEntries(url.searchParams)
)

treePatch = [finalSegment, ...rest]
}

// Create new tree based on the flightSegmentPath and router state patch
Expand Down
9 changes: 7 additions & 2 deletions test/e2e/app-dir/searchparams-reuse-loading/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ export default function Page({ searchParams }) {
</Link>
</li>
<li>
<Link href="/other-page" prefetch={false}>
/other-page
<Link href="/params-first" prefetch={false}>
/params-first
</Link>
</li>
<li>
<Link href="/root-page-first" prefetch={false}>
/root-page-first
</Link>
</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ export default async function Home({ searchParams }) {
</h1>

<div>
<Link href="/other-page?page=2" replace>
<Link href="/params-first?page=2" replace>
page 2
</Link>
</div>
<div>
<Link href="/other-page?page=3" replace>
<Link href="/params-first?page=3" replace>
page 3
</Link>
</div>
<div>
<Link href="/other-page?page=4" replace>
<Link href="/params-first?page=4" replace>
page 4
</Link>
</div>
<div>
<Link href="/other-page" replace>
<Link href="/params-first" replace>
No Params
</Link>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Link from 'next/link'

export default async function Home({ searchParams }) {
return (
<div>
<h1>
{searchParams.page ? (
<>You are on page {JSON.stringify(searchParams.page)}.</>
) : (
<>You are on the root page.</>
)}
</h1>

<div>
<Link href="/root-page-first" replace>
No Params
</Link>
</div>
<div>
<Link href="/root-page-first?page=2" replace>
page 2
</Link>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,46 @@ describe('searchparams-reuse-loading', () => {
})

it('should reflect the correct searchParams when re-using the same page segment', async () => {
const browser = await next.browser('/other-page')
await browser.elementByCss("[href='/other-page?page=2']").click()
const browser = await next.browser('/')
await browser.elementByCss("[href='/params-first']").click()
await browser.elementByCss("[href='/params-first?page=2']").click()
await retry(async () => {
expect(await browser.url()).toContain('/other-page?page=2')
expect(await browser.url()).toContain('/params-first?page=2')
})
expect(await browser.elementByCss('h1').text()).toBe('You are on page "2".')
await browser.elementByCss("[href='/other-page?page=3']").click()
await browser.elementByCss("[href='/params-first?page=3']").click()
await retry(async () => {
expect(await browser.url()).toContain('/other-page?page=3')
expect(await browser.url()).toContain('/params-first?page=3')
})
expect(await browser.elementByCss('h1').text()).toBe('You are on page "3".')
await browser.elementByCss("[href='/other-page?page=4']").click()
await browser.elementByCss("[href='/params-first?page=4']").click()
await retry(async () => {
expect(await browser.url()).toContain('/other-page?page=4')
expect(await browser.url()).toContain('/params-first?page=4')
})
expect(await browser.elementByCss('h1').text()).toBe('You are on page "4".')
await browser.elementByCss("[href='/other-page']").click()
await browser.elementByCss("[href='/params-first']").click()
await retry(async () => {
const currentUrl = new URL(await browser.url())
expect(currentUrl.pathname).toBe('/other-page')
expect(currentUrl.pathname).toBe('/params-first')
expect(currentUrl.search).toBe('')
})
expect(await browser.elementByCss('h1').text()).toBe(
'You are on the root page.'
)
})

it('should reflect the correct searchParams when the root page is prefetched first', async () => {
const browser = await next.browser('/')
await browser.elementByCss("[href='/root-page-first']").click()
await browser.elementByCss("[href='/root-page-first?page=2']").click()
await retry(async () => {
expect(await browser.url()).toContain('/root-page-first?page=2')
})
expect(await browser.elementByCss('h1').text()).toBe('You are on page "2".')
await browser.elementByCss("[href='/root-page-first']").click()
await retry(async () => {
const currentUrl = new URL(await browser.url())
expect(currentUrl.pathname).toBe('/root-page-first')
expect(currentUrl.search).toBe('')
})
expect(await browser.elementByCss('h1').text()).toBe(
Expand Down
Loading