fix: server edit view components don't receive document id prop (#13526)

### What?

Make the document `id` available to server-rendered admin components
that expect it—specifically `EditMenuItems` and
`BeforeDocumentControls`—so `props.id` matches the official docs.

### Why?

The docs show examples using `props.id`, but the runtime `serverProps`
and TS types didn’t include it. This led to `undefined` at render time.

### How?

- Add id to ServerProps and set it in renderDocumentSlots from
req.routeParams.id.

Fixes #13420
This commit is contained in:
Patrik
2025-08-21 13:23:51 -04:00
committed by GitHub
parent 5cf215d9cb
commit 96074530b1
6 changed files with 70 additions and 9 deletions

View File

@@ -293,7 +293,6 @@ Here's an example of a custom `editMenuItems` component:
```tsx ```tsx
import React from 'react' import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditMenuItemsServerProps } from 'payload' import type { EditMenuItemsServerProps } from 'payload'
@@ -301,12 +300,12 @@ export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}` const href = `/custom-action?id=${props.id}`
return ( return (
<PopupList.ButtonGroup> <>
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button> <a href={href}>Custom Edit Menu Item</a>
<PopupList.Button href={href}> <a href={href}>
Another Custom Edit Menu Item - add as many as you need! Another Custom Edit Menu Item - add as many as you need!
</PopupList.Button> </a>
</PopupList.ButtonGroup> </>
) )
} }
``` ```

View File

@@ -1,6 +1,5 @@
import type { import type {
BeforeDocumentControlsServerPropsOnly, BeforeDocumentControlsServerPropsOnly,
DefaultServerFunctionArgs,
DocumentSlots, DocumentSlots,
EditMenuItemsServerPropsOnly, EditMenuItemsServerPropsOnly,
PayloadRequest, PayloadRequest,
@@ -39,6 +38,7 @@ export const renderDocumentSlots: (args: {
const isPreviewEnabled = collectionConfig?.admin?.preview || globalConfig?.admin?.preview const isPreviewEnabled = collectionConfig?.admin?.preview || globalConfig?.admin?.preview
const serverProps: ServerProps = { const serverProps: ServerProps = {
id: req.routeParams.id as number | string,
i18n: req.i18n, i18n: req.i18n,
payload: req.payload, payload: req.payload,
user: req.user, user: req.user,

View File

@@ -402,6 +402,7 @@ export type Params = { [key: string]: string | string[] | undefined }
export type ServerProps = { export type ServerProps = {
readonly documentSubViewType?: DocumentSubViewTypes readonly documentSubViewType?: DocumentSubViewTypes
readonly i18n: I18nClient readonly i18n: I18nClient
readonly id?: number | string
readonly locale?: Locale readonly locale?: Locale
readonly params?: Params readonly params?: Params
readonly payload: Payload readonly payload: Payload

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { editMenuItemsSlug, postsCollectionSlug, uploadCollectionSlug } from '../slugs.js' import { editMenuItemsSlug } from '../slugs.js'
export const EditMenuItems: CollectionConfig = { export const EditMenuItems: CollectionConfig = {
slug: editMenuItemsSlug, slug: editMenuItemsSlug,
@@ -11,6 +11,9 @@ export const EditMenuItems: CollectionConfig = {
{ {
path: '/components/EditMenuItems/index.js#EditMenuItems', path: '/components/EditMenuItems/index.js#EditMenuItems',
}, },
{
path: '/components/EditMenuItemsServer/index.js#EditMenuItemsServer',
},
], ],
}, },
}, },

View File

@@ -0,0 +1,13 @@
import type { EditMenuItemsServerProps } from 'payload'
import React from 'react'
export const EditMenuItemsServer = (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<div>
<a href={href}>Custom Edit Menu Item (Server)</a>
</div>
)
}

View File

@@ -731,7 +731,7 @@ describe('Document View', () => {
}) })
describe('custom editMenuItem components', () => { describe('custom editMenuItem components', () => {
test('should render custom editMenuItems component', async () => { test('should render custom editMenuItems client component', async () => {
await page.goto(editMenuItemsURL.create) await page.goto(editMenuItemsURL.create)
await page.locator('#field-title')?.fill(title) await page.locator('#field-title')?.fill(title)
await saveDocAndAssert(page) await saveDocAndAssert(page)
@@ -746,6 +746,51 @@ describe('Document View', () => {
await expect(customEditMenuItem).toBeVisible() await expect(customEditMenuItem).toBeVisible()
}) })
test('should render custom editMenuItems server component', async () => {
await page.goto(editMenuItemsURL.create)
await page.locator('#field-title')?.fill(title)
await saveDocAndAssert(page)
const threeDotMenu = page.getByRole('main').locator('.doc-controls__popup')
await expect(threeDotMenu).toBeVisible()
await threeDotMenu.click()
const popup = page.locator('.popup--active .popup__content')
await expect(popup).toBeVisible()
const customEditMenuItem = popup.getByRole('link', {
name: 'Custom Edit Menu Item (Server)',
})
await expect(customEditMenuItem).toBeVisible()
})
test('should render doc id in href of custom editMenuItems server component link', async () => {
await page.goto(editMenuItemsURL.create)
await page.locator('#field-title')?.fill(title)
await saveDocAndAssert(page)
const threeDotMenu = page.getByRole('main').locator('.doc-controls__popup')
await expect(threeDotMenu).toBeVisible()
await threeDotMenu.click()
const popup = page.locator('.popup--active .popup__content')
await expect(popup).toBeVisible()
const customEditMenuItem = popup.getByRole('link', {
name: 'Custom Edit Menu Item (Server)',
})
await expect(customEditMenuItem).toBeVisible()
// Extract the document id from the edit page URL (last path segment)
const editPath = new URL(page.url()).pathname
const docId = editPath.split('/').filter(Boolean).pop()!
// Assert the href contains the same id
await expect(customEditMenuItem).toHaveAttribute('href', `/custom-action?id=${docId}`)
})
}) })
}) })