perf: list view table should not send duplicative client CollectionConfig to client (#10664)

Previously, we were unnecessarily passing the `ClientCollectionConfig`
down from the Table to the Client, even though the client cell
components could simply access it via the `useConfig` hook. This
resulted in redundant data being sent to the client for every single
table cell. Additionally, we were performing a deep copy of the
`ClientCollectionConfig`, which wasted both memory and CPU resources on
the server.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This commit is contained in:
Alessio Gravili
2025-01-20 14:55:52 -07:00
committed by GitHub
parent 711febcc90
commit 823e223786
10 changed files with 68 additions and 38 deletions

View File

@@ -1,6 +1,5 @@
import type { I18nClient } from '@payloadcms/translations'
import type { ClientCollectionConfig } from '../../collections/config/client.js'
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
import type {
ArrayFieldClient,
@@ -71,24 +70,25 @@ export type DefaultCellComponentProps<
? { x: number; y: number }
: any
: TCellData
readonly className?: string
readonly collectionConfig: ClientCollectionConfig
readonly columnIndex?: number
readonly customCellProps?: Record<string, any>
readonly field: TField
readonly link?: boolean
readonly onClick?: (args: {
className?: string
collectionSlug: SanitizedCollectionConfig['slug']
columnIndex?: number
customCellProps?: Record<string, any>
field: TField
link?: boolean
onClick?: (args: {
cellData: unknown
collectionSlug: SanitizedCollectionConfig['slug']
rowData: RowData
}) => void
readonly rowData: RowData
rowData: RowData
}
export type DefaultServerCellComponentProps<
TField extends ClientField = ClientField,
TCellData = any,
> = {
collectionConfig: SanitizedCollectionConfig
field: Field
i18n: I18nClient
payload: Payload

View File

@@ -1,7 +1,7 @@
import type { EditorConfig as LexicalEditorConfig, SerializedEditorState } from 'lexical'
import type {
ClientField,
DefaultCellComponentProps,
DefaultServerCellComponentProps,
LabelFunction,
RichTextAdapter,
RichTextFieldClient,
@@ -116,7 +116,7 @@ export type LexicalRichTextFieldProps = {
} & Pick<ServerFieldBase, 'permissions'> &
RichTextFieldClientProps<SerializedEditorState, AdapterProps, object>
export type LexicalRichTextCellProps = DefaultCellComponentProps<
export type LexicalRichTextCellProps = DefaultServerCellComponentProps<
RichTextFieldClient<SerializedEditorState, AdapterProps, object>,
SerializedEditorState
>

View File

@@ -1,4 +1,4 @@
import type { DefaultCellComponentProps, Payload } from 'payload'
import type { DefaultServerCellComponentProps, Payload } from 'payload'
import { getTranslation, type I18nClient } from '@payloadcms/translations'
import { formatAdminURL } from '@payloadcms/ui/shared'
@@ -11,7 +11,7 @@ export const RscEntrySlateCell: React.FC<
{
i18n: I18nClient
payload: Payload
} & DefaultCellComponentProps
} & DefaultServerCellComponentProps
> = (props) => {
const {
cellData,

View File

@@ -20,7 +20,7 @@ export const DrawerLink: React.FC<{
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
id: cellProps?.rowData.id,
collectionSlug: cellProps?.collectionConfig.slug,
collectionSlug: cellProps?.collectionSlug,
})
const onDrawerSave = useCallback<DocumentDrawerProps['onSave']>(

View File

@@ -1,11 +1,12 @@
'use client'
import type { CodeFieldClient, DefaultCellComponentProps } from 'payload'
import type { ClientCollectionConfig, CodeFieldClient, DefaultCellComponentProps } from 'payload'
import React from 'react'
import './index.scss'
export interface CodeCellProps extends DefaultCellComponentProps<CodeFieldClient> {
readonly collectionConfig: ClientCollectionConfig
readonly nowrap?: boolean
}

View File

@@ -1,5 +1,10 @@
'use client'
import type { DefaultCellComponentProps, TextFieldClient, UploadFieldClient } from 'payload'
import type {
ClientCollectionConfig,
DefaultCellComponentProps,
TextFieldClient,
UploadFieldClient,
} from 'payload'
import React from 'react'
@@ -9,7 +14,9 @@ import './index.scss'
const baseClass = 'file'
export interface FileCellProps
extends DefaultCellComponentProps<TextFieldClient | UploadFieldClient> {}
extends DefaultCellComponentProps<TextFieldClient | UploadFieldClient> {
readonly collectionConfig: ClientCollectionConfig
}
export const FileCell: React.FC<FileCellProps> = ({
cellData: filename,

View File

@@ -119,6 +119,7 @@ export const RelationshipCell: React.FC<RelationshipCellProps> = ({
<FileCell
cellData={label}
collectionConfig={relatedCollection}
collectionSlug={relatedCollection.slug}
customCellProps={customCellContext}
field={field}
rowData={document}

View File

@@ -1,5 +1,5 @@
'use client'
import type { DefaultCellComponentProps, UploadFieldClient } from 'payload'
import type { ClientCollectionConfig, DefaultCellComponentProps, UploadFieldClient } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import LinkImport from 'next/link.js'
@@ -17,7 +17,7 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
const {
cellData,
className: classNameFromProps,
collectionConfig,
collectionSlug,
field,
field: { admin },
link,
@@ -31,8 +31,11 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
config: {
routes: { admin: adminRoute },
},
getEntityConfig,
} = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const classNameFromConfigContext = admin && 'className' in admin ? admin.className : undefined
const className =
@@ -83,6 +86,7 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
<CodeCell
cellData={`ID: ${cellData}`}
collectionConfig={collectionConfig}
collectionSlug={collectionSlug}
field={{
...field,
type: 'code',

View File

@@ -5,8 +5,8 @@ import React from 'react'
import { useListDrawerContext } from '../../../elements/ListDrawer/Provider.js'
import { DefaultCell } from '../../Table/DefaultCell/index.js'
import { useTableColumns } from '../index.js'
import './index.scss'
import { useTableColumns } from '../index.js'
const baseClass = 'default-cell'
@@ -24,7 +24,10 @@ export const RenderDefaultCell: React.FC<{
const { drawerSlug, onSelect } = useListDrawerContext()
const { LinkedCellOverride } = useTableColumns()
const propsToPass = { ...clientProps, columnIndex }
const propsToPass: DefaultCellComponentProps = {
...clientProps,
columnIndex,
}
if (isLinkedColumn && drawerSlug) {
propsToPass.className = `${baseClass}__first-cell`

View File

@@ -16,7 +16,6 @@ import type {
import { MissingEditorProp } from 'payload'
import {
deepCopyObjectSimple,
fieldIsHiddenOrDisabled,
fieldIsID,
fieldIsPresentationalOnly,
@@ -171,7 +170,7 @@ export const buildColumnState = (args: Args): Column[] => {
field,
}
const serverProps: Pick<
const customLabelServerProps: Pick<
ServerComponentProps,
'clientField' | 'collectionSlug' | 'field' | 'i18n' | 'payload'
> = {
@@ -187,7 +186,7 @@ export const buildColumnState = (args: Args): Column[] => {
clientProps,
Component: CustomLabelToRender,
importMap: payload.importMap,
serverProps,
serverProps: customLabelServerProps,
})
: undefined
@@ -208,7 +207,7 @@ export const buildColumnState = (args: Args): Column[] => {
const baseCellClientProps: DefaultCellComponentProps = {
cellData: undefined,
collectionConfig: deepCopyObjectSimple(clientCollectionConfig),
collectionSlug: clientCollectionConfig.slug,
customCellProps,
field,
rowData: undefined,
@@ -231,6 +230,21 @@ export const buildColumnState = (args: Args): Column[] => {
rowData: doc,
}
const cellServerProps: DefaultServerCellComponentProps = {
cellData: cellClientProps.cellData,
className: baseCellClientProps.className,
collectionConfig,
collectionSlug: collectionConfig.slug,
columnIndex: baseCellClientProps.columnIndex,
customCellProps: baseCellClientProps.customCellProps,
field: _field,
i18n,
link: cellClientProps.link,
onClick: baseCellClientProps.onClick,
payload,
rowData: doc,
}
let CustomCell = null
if (_field?.type === 'richText') {
@@ -254,21 +268,21 @@ export const buildColumnState = (args: Args): Column[] => {
clientProps: cellClientProps,
Component: _field.editor.CellComponent,
importMap: payload.importMap,
serverProps,
serverProps: cellServerProps,
})
} else {
CustomCell =
_field?.admin && 'components' in _field.admin && _field.admin.components?.Cell
? RenderServerComponent({
const CustomCellComponent = _field?.admin?.components?.Cell
if (CustomCellComponent) {
CustomCell = RenderServerComponent({
clientProps: cellClientProps,
Component:
_field?.admin &&
'components' in _field.admin &&
_field.admin.components?.Cell,
Component: CustomCellComponent,
importMap: payload.importMap,
serverProps,
serverProps: cellServerProps,
})
: undefined
} else {
CustomCell = undefined
}
}
return (