From f9121c1a3a3a6700b11eb358aa200ee13f1320a5 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 24 Feb 2025 14:49:52 +0000 Subject: [PATCH] fix(ui): link element triggering clicks twice (#11362) Fixes https://github.com/payloadcms/payload/issues/11359#issuecomment-2678213414 The link element by using startTransitionRoute and manually calling router.push would technically cause links to be clicked twice, by not preventing default browser behaviour. This caused a problem on clicking /create links as it hit the route twice. Added a test making sure Create new doesn't lead to abnormally increased document counts Changes: - Added `e.preventDefault()` in our Link element - Added `preventDefault` as an optional prop to this element so that people can handle it on their own if needed via a custom `onClick` --- packages/ui/src/elements/Link/index.tsx | 18 ++++++++++- test/versions/e2e.spec.ts | 43 ++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/elements/Link/index.tsx b/packages/ui/src/elements/Link/index.tsx index 4fbd1b2fa4..5ab5f9d139 100644 --- a/packages/ui/src/elements/Link/index.tsx +++ b/packages/ui/src/elements/Link/index.tsx @@ -23,10 +23,20 @@ function isModifiedEvent(event: React.MouseEvent): boolean { ) } -export const Link: React.FC[0]> = ({ +type Props = { + /** + * Disable the e.preventDefault() call on click if you want to handle it yourself via onClick + * + * @default true + */ + preventDefault?: boolean +} & Parameters[0] + +export const Link: React.FC = ({ children, href, onClick, + preventDefault = true, ref, replace, scroll, @@ -47,6 +57,12 @@ export const Link: React.FC[0]> = ({ onClick(e) } + // We need a preventDefault here so that a clicked link doesn't trigger twice, + // once for default browser navigation and once for startRouteTransition + if (preventDefault) { + e.preventDefault() + } + startRouteTransition(() => { const url = typeof href === 'string' ? href : formatUrl(href) diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 0cd34eb850..d7d44c0842 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -438,6 +438,47 @@ describe('Versions', () => { await expect(drawer.locator('.id-label')).toBeVisible() }) + test('collection - autosave - should not create duplicates when clicking Create new', async () => { + // This test checks that when we click "Create new" in the list view, it only creates 1 extra document and not more + const { totalDocs: initialDocsCount } = await payload.find({ + collection: autosaveCollectionSlug, + draft: true, + }) + + await page.goto(autosaveURL.create) + await page.locator('#field-title').fill('autosave title') + await waitForAutoSaveToRunAndComplete(page) + await expect(page.locator('#field-title')).toHaveValue('autosave title') + + const { totalDocs: updatedDocsCount } = await payload.find({ + collection: autosaveCollectionSlug, + draft: true, + }) + + await expect(() => { + expect(updatedDocsCount).toBe(initialDocsCount + 1) + }).toPass({ timeout: POLL_TOPASS_TIMEOUT, intervals: [100] }) + + await page.goto(autosaveURL.list) + const createNewButton = page.locator('.list-header .btn:has-text("Create New")') + await createNewButton.click() + + await page.waitForURL(`**/${autosaveCollectionSlug}/**`) + + await page.locator('#field-title').fill('autosave title') + await waitForAutoSaveToRunAndComplete(page) + await expect(page.locator('#field-title')).toHaveValue('autosave title') + + const { totalDocs: latestDocsCount } = await payload.find({ + collection: autosaveCollectionSlug, + draft: true, + }) + + await expect(() => { + expect(latestDocsCount).toBe(updatedDocsCount + 1) + }).toPass({ timeout: POLL_TOPASS_TIMEOUT, intervals: [100] }) + }) + test('collection - should update updatedAt', async () => { await page.goto(url.create) await page.waitForURL(`**/${url.create}`) @@ -757,7 +798,7 @@ describe('Versions', () => { // schedule publish should not be available before document has been saved await page.locator('#action-save-popup').click() - await expect(page.locator('#schedule-publish')).not.toBeVisible() + await expect(page.locator('#schedule-publish')).toBeHidden() // save draft then try to schedule publish await saveDocAndAssert(page)