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

View File

@@ -1,7 +1,7 @@
import type { EditorConfig as LexicalEditorConfig, SerializedEditorState } from 'lexical' import type { EditorConfig as LexicalEditorConfig, SerializedEditorState } from 'lexical'
import type { import type {
ClientField, ClientField,
DefaultCellComponentProps, DefaultServerCellComponentProps,
LabelFunction, LabelFunction,
RichTextAdapter, RichTextAdapter,
RichTextFieldClient, RichTextFieldClient,
@@ -116,7 +116,7 @@ export type LexicalRichTextFieldProps = {
} & Pick<ServerFieldBase, 'permissions'> & } & Pick<ServerFieldBase, 'permissions'> &
RichTextFieldClientProps<SerializedEditorState, AdapterProps, object> RichTextFieldClientProps<SerializedEditorState, AdapterProps, object>
export type LexicalRichTextCellProps = DefaultCellComponentProps< export type LexicalRichTextCellProps = DefaultServerCellComponentProps<
RichTextFieldClient<SerializedEditorState, AdapterProps, object>, RichTextFieldClient<SerializedEditorState, AdapterProps, object>,
SerializedEditorState 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 { getTranslation, type I18nClient } from '@payloadcms/translations'
import { formatAdminURL } from '@payloadcms/ui/shared' import { formatAdminURL } from '@payloadcms/ui/shared'
@@ -11,7 +11,7 @@ export const RscEntrySlateCell: React.FC<
{ {
i18n: I18nClient i18n: I18nClient
payload: Payload payload: Payload
} & DefaultCellComponentProps } & DefaultServerCellComponentProps
> = (props) => { > = (props) => {
const { const {
cellData, cellData,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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