-
Notifications
You must be signed in to change notification settings - Fork 26.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix bugs pertaining to server actions + navigation (#55853)
This fixes some scenarios where executing a server action after navigation can cause the action to behave incorrectly (double submitting, not resolving). There are two separate issues: - `canonicalUrl` and `pendingNavigatePath` were not constructed using the same function (`createHrefFromUrl`) so in certain situations they'd be comparing different values - a fulfilled inFlightServerAction should not be invoked again Closes NEXT-1655 Closes NEXT-1654 Fixes #55845 Fixes #55814 Fixes #55805
- Loading branch information
Showing
17 changed files
with
193 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
test/e2e/app-dir/actions-navigation/app/action-after-redirect/actions.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
'use server' | ||
|
||
export async function expensiveCalculation() { | ||
console.log('server action invoked') | ||
// sleep for 1 second | ||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
|
||
return Math.random() | ||
} |
35 changes: 35 additions & 0 deletions
35
test/e2e/app-dir/actions-navigation/app/action-after-redirect/form.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use client' | ||
|
||
import React from 'react' | ||
import { expensiveCalculation } from './actions' | ||
|
||
export function Form({ randomNum }) { | ||
const [isPending, setIsPending] = React.useState(false) | ||
const [result, setResult] = React.useState(null) | ||
|
||
async function handleSubmit(event) { | ||
event.preventDefault() | ||
|
||
setIsPending(true) | ||
const result = await expensiveCalculation() | ||
setIsPending(false) | ||
setResult(result) | ||
} | ||
|
||
return ( | ||
<form | ||
style={{ display: 'flex', gap: '2rem', flexDirection: 'column' }} | ||
id="form" | ||
onSubmit={handleSubmit} | ||
> | ||
<section> | ||
<button style={{ width: 'max-content' }} type="submit" id="submit"> | ||
Submit | ||
</button> | ||
{isPending && 'Loading...'} | ||
</section> | ||
<div>Server side rendered number: {randomNum}</div> | ||
{result && <div id="result">RESULT FROM SERVER ACTION: {result}</div>} | ||
</form> | ||
) | ||
} |
11 changes: 11 additions & 0 deletions
11
test/e2e/app-dir/actions-navigation/app/action-after-redirect/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Form } from './form' | ||
|
||
export default async function Page() { | ||
const randomNum = Math.random() | ||
|
||
return ( | ||
<div> | ||
<Form randomNum={randomNum} /> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export default function RootLayout({ children }) { | ||
return ( | ||
<html> | ||
<head /> | ||
<body>{children}</body> | ||
</html> | ||
) | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/actions-navigation/app/middleware-redirect/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Page() { | ||
return <div>Hello World</div> | ||
} |
5 changes: 5 additions & 0 deletions
5
...app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/actions.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use server' | ||
|
||
export async function addToCart() { | ||
console.log('addToCart') | ||
} |
24 changes: 24 additions & 0 deletions
24
...2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
'use client' | ||
import { experimental_useFormStatus as useFormStatus } from 'react-dom' | ||
import { addToCart } from './actions' | ||
|
||
function SubmitButton() { | ||
const { pending } = useFormStatus() | ||
|
||
return ( | ||
<button type="submit" aria-disabled={pending}> | ||
Add to cart | ||
</button> | ||
) | ||
} | ||
|
||
export default async function Page() { | ||
return ( | ||
<> | ||
<h1>Add to cart</h1> | ||
<form action={addToCart}> | ||
<SubmitButton /> | ||
</form> | ||
</> | ||
) | ||
} |
5 changes: 5 additions & 0 deletions
5
test/e2e/app-dir/actions-navigation/app/nested-folder/[slug]/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Link from 'next/link' | ||
|
||
export default async function Page() { | ||
return <Link href="../nested-folder/product-category/machine">Machines</Link> | ||
} |
7 changes: 7 additions & 0 deletions
7
test/e2e/app-dir/actions-navigation/app/nested-folder/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import Link from 'next/link' | ||
|
||
export default function Page() { | ||
return ( | ||
<Link href="../action-after-redirect">Go to ../action-after-redirect</Link> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Link from 'next/link' | ||
|
||
export default function Page() { | ||
return ( | ||
<Link href="/middleware-redirect" id="middleware-redirect"> | ||
Go to /middleware-redirect | ||
</Link> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { createNextDescribe } from 'e2e-utils' | ||
import { check, waitFor } from 'next-test-utils' | ||
|
||
createNextDescribe( | ||
'app-dir action handling', | ||
{ | ||
files: __dirname, | ||
}, | ||
({ next }) => { | ||
it('should handle actions correctly after navigation / redirection events', async () => { | ||
const browser = await next.browser('/') | ||
|
||
await browser.elementByCss('#middleware-redirect').click() | ||
|
||
expect(await browser.elementByCss('#form').text()).not.toContain( | ||
'Loading...' | ||
) | ||
|
||
await browser.elementByCss('#submit').click() | ||
|
||
await check(() => { | ||
return browser.elementByCss('#form').text() | ||
}, /Loading.../) | ||
|
||
// wait for 2 seconds, since the action takes a second to resolve | ||
await waitFor(2000) | ||
|
||
expect(await browser.elementByCss('#form').text()).not.toContain( | ||
'Loading...' | ||
) | ||
|
||
expect(await browser.elementByCss('#result').text()).toContain( | ||
'RESULT FROM SERVER ACTION' | ||
) | ||
}) | ||
|
||
it('should handle actions correctly after following a relative link', async () => { | ||
const browser = await next.browser('/nested-folder/products') | ||
|
||
await browser.elementByCss('a').click() | ||
|
||
await browser.elementByCss('button').click() | ||
|
||
await check(() => { | ||
return (next.cliOutput.match(/addToCart/g) || []).length | ||
}, 1) | ||
}) | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { NextResponse } from 'next/server' | ||
|
||
export function middleware(request) { | ||
if (request.nextUrl.pathname.startsWith('/middleware-redirect')) { | ||
return NextResponse.redirect(new URL('/action-after-redirect', request.url)) | ||
} | ||
} | ||
|
||
export const matcher = ['/middleware-redirect'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
experimental: { | ||
serverActions: true, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Link from 'next/link' | ||
|
||
export default function Page() { | ||
return ( | ||
<div> | ||
<Link href="/middleware-redirect">Go to /middleware-redirect</Link> | ||
</div> | ||
) | ||
} |