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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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']>(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user