perf: optimize getEntityConfig lookups (#10665)

Replaces array-based lookups in `getEntityConfig` with a map, reducing
time complexity from O(n) to O(1).
This commit is contained in:
Alessio Gravili
2025-01-20 12:32:38 -07:00
committed by GitHub
parent 56667cdc8d
commit c07c9e9129
50 changed files with 183 additions and 181 deletions

View File

@@ -831,6 +831,22 @@ const MyComponent: React.FC = () => {
} }
``` ```
If you need to retrieve a specific collection or global config by its slug, `getEntityConfig` is the most efficient way to do so:
```tsx
'use client'
import { useConfig } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { getEntityConfig } = useConfig()
const mediaConfig = getEntityConfig({ collectionSlug: 'media'})
// highlight-end
return <span>The media collection has {mediaConfig.fields.length} fields.</span>
}
```
## useEditDepth ## useEditDepth
Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases. Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases.

View File

@@ -44,7 +44,7 @@ export function getRouteInfo({
let idType = defaultIDType let idType = defaultIDType
if (collectionSlug) { if (collectionSlug) {
collectionConfig = config.collections.find((collection) => collection.slug === collectionSlug) collectionConfig = payload.collections?.[collectionSlug]?.config
} }
if (globalSlug) { if (globalSlug) {

View File

@@ -1,7 +1,5 @@
'use client' 'use client'
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
import { import {
CheckboxField, CheckboxField,
CopyToClipboard, CopyToClipboard,
@@ -41,8 +39,8 @@ export const APIViewClient: React.FC = () => {
getEntityConfig, getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig const globalConfig = getEntityConfig({ globalSlug })
const localeOptions = const localeOptions =
localization && localization &&

View File

@@ -42,7 +42,7 @@ export const Account: React.FC<AdminViewProps> = async ({
serverURL, serverURL,
} = config } = config
const collectionConfig = config.collections.find((collection) => collection.slug === userSlug) const collectionConfig = payload?.collections?.[userSlug]?.config
if (collectionConfig && user?.id) { if (collectionConfig && user?.id) {
// Fetch the data required for the view // Fetch the data required for the view

View File

@@ -1,7 +1,6 @@
'use client' 'use client'
import type { FormProps, UserWithToken } from '@payloadcms/ui' import type { FormProps, UserWithToken } from '@payloadcms/ui'
import type { import type {
ClientCollectionConfig,
DocumentPreferences, DocumentPreferences,
FormState, FormState,
LoginWithUsernameOptions, LoginWithUsernameOptions,
@@ -45,7 +44,7 @@ export const CreateFirstUserClient: React.FC<{
const abortOnChangeRef = React.useRef<AbortController>(null) const abortOnChangeRef = React.useRef<AbortController>(null)
const collectionConfig = getEntityConfig({ collectionSlug: userSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug: userSlug })
const onChange: FormProps['onChange'][0] = React.useCallback( const onChange: FormProps['onChange'][0] = React.useCallback(
async ({ formState: prevFormState }) => { async ({ formState: prevFormState }) => {

View File

@@ -17,15 +17,15 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
req, req,
req: { req: {
payload: { payload: {
collections,
config: { config: {
admin: { user: userSlug }, admin: { user: userSlug },
}, },
config,
}, },
}, },
} = initPageResult } = initPageResult
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug) const collectionConfig = collections?.[userSlug]?.config
const { auth: authOptions } = collectionConfig const { auth: authOptions } = collectionConfig
const loginWithUsername = authOptions.loginWithUsername const loginWithUsername = authOptions.loginWithUsername

View File

@@ -148,9 +148,7 @@ export const renderDocumentHandler = async (args: {
importMap: payload.importMap, importMap: payload.importMap,
initialData, initialData,
initPageResult: { initPageResult: {
collectionConfig: payload.config.collections.find( collectionConfig: payload?.collections?.[collectionSlug]?.config,
(collection) => collection.slug === collectionSlug,
),
cookies, cookies,
docID, docID,
globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug), globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug),

View File

@@ -10,7 +10,7 @@ import React, { useState } from 'react'
import { FormHeader } from '../../../elements/FormHeader/index.js' import { FormHeader } from '../../../elements/FormHeader/index.js'
export const ForgotPasswordForm: React.FC = () => { export const ForgotPasswordForm: React.FC = () => {
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { const {
admin: { user: userSlug }, admin: { user: userSlug },
@@ -19,7 +19,7 @@ export const ForgotPasswordForm: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [hasSubmitted, setHasSubmitted] = useState(false) const [hasSubmitted, setHasSubmitted] = useState(false)
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug) const collectionConfig = getEntityConfig({ collectionSlug: userSlug })
const loginWithUsername = collectionConfig?.auth?.loginWithUsername const loginWithUsername = collectionConfig?.auth?.loginWithUsername
const handleResponse: FormProps['handleResponse'] = (res, successToast, errorToast) => { const handleResponse: FormProps['handleResponse'] = (res, successToast, errorToast) => {

View File

@@ -139,9 +139,7 @@ export const renderListHandler = async (args: {
enableRowSelections, enableRowSelections,
importMap: payload.importMap, importMap: payload.importMap,
initPageResult: { initPageResult: {
collectionConfig: payload.config.collections.find( collectionConfig: payload?.collections?.[collectionSlug]?.config,
(collection) => collection.slug === collectionSlug,
),
cookies, cookies,
globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug), globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug),
languageOptions: undefined, // TODO languageOptions: undefined, // TODO

View File

@@ -567,9 +567,9 @@ export const LivePreviewClient: React.FC<
url, url,
}) })
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig const globalConfig = getEntityConfig({ globalSlug })
const schemaPath = collectionSlug || globalSlug const schemaPath = collectionSlug || globalSlug

View File

@@ -24,7 +24,7 @@ export const LoginForm: React.FC<{
prefillUsername?: string prefillUsername?: string
searchParams: { [key: string]: string | string[] | undefined } searchParams: { [key: string]: string | string[] | undefined }
}> = ({ prefillEmail, prefillPassword, prefillUsername, searchParams }) => { }> = ({ prefillEmail, prefillPassword, prefillUsername, searchParams }) => {
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { const {
admin: { admin: {
@@ -34,7 +34,7 @@ export const LoginForm: React.FC<{
routes: { admin: adminRoute, api: apiRoute }, routes: { admin: adminRoute, api: apiRoute },
} = config } = config
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug) const collectionConfig = getEntityConfig({ collectionSlug: userSlug })
const { auth: authOptions } = collectionConfig const { auth: authOptions } = collectionConfig
const loginWithUsername = authOptions.loginWithUsername const loginWithUsername = authOptions.loginWithUsername
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername) const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)

View File

@@ -32,7 +32,7 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, params, se
redirect((searchParams.redirect as string) || admin) redirect((searchParams.redirect as string) || admin)
} }
const collectionConfig = collections.find(({ slug }) => slug === userSlug) const collectionConfig = payload?.collections?.[userSlug]?.config
const prefillAutoLogin = const prefillAutoLogin =
typeof config.admin?.autoLogin === 'object' && config.admin?.autoLogin.prefillOnly typeof config.admin?.autoLogin === 'object' && config.admin?.autoLogin.prefillOnly

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { ClientCollectionConfig, ClientGlobalConfig, OptionObject } from 'payload' import type { OptionObject } from 'payload'
import { Gutter, useConfig, useDocumentInfo, usePayloadAPI, useTranslation } from '@payloadcms/ui' import { Gutter, useConfig, useDocumentInfo, usePayloadAPI, useTranslation } from '@payloadcms/ui'
import { formatDate } from '@payloadcms/ui/shared' import { formatDate } from '@payloadcms/ui/shared'
@@ -31,11 +31,9 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
const { i18n } = useTranslation() const { i18n } = useTranslation()
const { id, collectionSlug, globalSlug } = useDocumentInfo() const { id, collectionSlug, globalSlug } = useDocumentInfo()
const [collectionConfig] = useState( const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug }))
() => getEntityConfig({ collectionSlug }) as ClientCollectionConfig,
)
const [globalConfig] = useState(() => getEntityConfig({ globalSlug }) as ClientGlobalConfig) const [globalConfig] = useState(() => getEntityConfig({ globalSlug }))
const [locales, setLocales] = useState<OptionObject[]>(localeOptions) const [locales, setLocales] = useState<OptionObject[]>(localeOptions)

View File

@@ -35,13 +35,13 @@ const Restore: React.FC<Props> = ({
}) => { }) => {
const { const {
config: { config: {
collections,
routes: { admin: adminRoute, api: apiRoute }, routes: { admin: adminRoute, api: apiRoute },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = collections.find((collection) => collection.slug === collectionSlug) const collectionConfig = getEntityConfig({ collectionSlug })
const { toggleModal } = useModal() const { toggleModal } = useModal()
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)

View File

@@ -18,9 +18,7 @@ export async function getAccessResults({
const isLoggedIn = !!user const isLoggedIn = !!user
const userCollectionConfig = const userCollectionConfig =
user && user.collection user && user.collection ? payload?.collections?.[user.collection]?.config : null
? payload.config.collections.find((collection) => collection.slug === user.collection)
: null
if (userCollectionConfig && payload.config.admin.user === user?.collection) { if (userCollectionConfig && payload.config.admin.user === user?.collection) {
results.canAccessAdmin = userCollectionConfig.access.admin results.canAccessAdmin = userCollectionConfig.access.admin

View File

@@ -23,9 +23,8 @@ export const previewHandler: PayloadHandler = async (req) => {
let previewURL: string let previewURL: string
const generatePreviewURL = req.payload.config.collections.find( const generatePreviewURL =
(config) => config.slug === collection.config.slug, req.payload?.collections?.[collection.config.slug]?.config?.admin?.preview
)?.admin?.preview
const token = extractJWT(req) const token = extractJWT(req)

View File

@@ -158,9 +158,7 @@ export const promise = async <T>({
if (Array.isArray(field.relationTo)) { if (Array.isArray(field.relationTo)) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((relatedDoc: { relationTo: string; value: JsonValue }, i) => { value.forEach((relatedDoc: { relationTo: string; value: JsonValue }, i) => {
const relatedCollection = req.payload.config.collections.find( const relatedCollection = req.payload.collections?.[relatedDoc.relationTo]?.config
(collection) => collection.slug === relatedDoc.relationTo,
)
if ( if (
typeof relatedDoc.value === 'object' && typeof relatedDoc.value === 'object' &&
@@ -185,9 +183,7 @@ export const promise = async <T>({
}) })
} }
if (field.hasMany !== true && valueIsValueWithRelation(value)) { if (field.hasMany !== true && valueIsValueWithRelation(value)) {
const relatedCollection = req.payload.config.collections.find( const relatedCollection = req.payload.collections?.[value.relationTo]?.config
(collection) => collection.slug === value.relationTo,
)
if (typeof value.value === 'object' && value.value && 'id' in value.value) { if (typeof value.value === 'object' && value.value && 'id' in value.value) {
value.value = (value.value as TypeWithID).id value.value = (value.value as TypeWithID).id
@@ -206,9 +202,9 @@ export const promise = async <T>({
} else { } else {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((relatedDoc: unknown, i) => { value.forEach((relatedDoc: unknown, i) => {
const relatedCollection = req.payload.config.collections.find( const relatedCollection = Array.isArray(field.relationTo)
(collection) => collection.slug === field.relationTo, ? undefined
) : req.payload.collections?.[field.relationTo]?.config
if (typeof relatedDoc === 'object' && relatedDoc && 'id' in relatedDoc) { if (typeof relatedDoc === 'object' && relatedDoc && 'id' in relatedDoc) {
value[i] = relatedDoc.id value[i] = relatedDoc.id
@@ -226,9 +222,7 @@ export const promise = async <T>({
}) })
} }
if (field.hasMany !== true && value) { if (field.hasMany !== true && value) {
const relatedCollection = req.payload.config.collections.find( const relatedCollection = req.payload.collections?.[field.relationTo]?.config
(collection) => collection.slug === field.relationTo,
)
if (typeof value === 'object' && value && 'id' in value) { if (typeof value === 'object' && value && 'id' in value) {
siblingData[field.name] = value.id siblingData[field.name] = value.id

View File

@@ -166,7 +166,7 @@ export const email: EmailFieldValidation = (
{ {
collectionSlug, collectionSlug,
req: { req: {
payload: { config }, payload: { collections },
t, t,
}, },
required, required,
@@ -174,7 +174,7 @@ export const email: EmailFieldValidation = (
}, },
) => { ) => {
if (collectionSlug) { if (collectionSlug) {
const collection = config.collections.find(({ slug }) => slug === collectionSlug) const collection = collections?.[collectionSlug]?.config
if ( if (
collection.auth.loginWithUsername && collection.auth.loginWithUsername &&
@@ -201,7 +201,7 @@ export const username: UsernameFieldValidation = (
{ {
collectionSlug, collectionSlug,
req: { req: {
payload: { config }, payload: { collections, config },
t, t,
}, },
required, required,
@@ -211,7 +211,7 @@ export const username: UsernameFieldValidation = (
let maxLength: number let maxLength: number
if (collectionSlug) { if (collectionSlug) {
const collection = config.collections.find(({ slug }) => slug === collectionSlug) const collection = collections?.[collectionSlug]?.config
if ( if (
collection.auth.loginWithUsername && collection.auth.loginWithUsername &&

View File

@@ -27,7 +27,7 @@ export const checkDocumentLockStatus = async ({
// Retrieve the lockDocuments property for either collection or global // Retrieve the lockDocuments property for either collection or global
const lockDocumentsProp = collectionSlug const lockDocumentsProp = collectionSlug
? payload.config?.collections?.find((c) => c.slug === collectionSlug)?.lockDocuments ? payload.collections?.[collectionSlug]?.config?.lockDocuments
: payload.config?.globals?.find((g) => g.slug === globalSlug)?.lockDocuments : payload.config?.globals?.find((g) => g.slug === globalSlug)?.lockDocuments
const isLockingEnabled = lockDocumentsProp !== false const isLockingEnabled = lockDocumentsProp !== false

View File

@@ -36,10 +36,10 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
const { const {
config: { config: {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const field: FieldType<string> = useField({ ...props, path } as Options) const field: FieldType<string> = useField({ ...props, path } as Options)
@@ -109,7 +109,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
const hasImage = Boolean(value) const hasImage = Boolean(value)
const collection = collections?.find((coll) => coll.slug === relationTo) || undefined const collection = getEntityConfig({ collectionSlug: relationTo })
return ( return (
<div <div

View File

@@ -62,7 +62,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
uuid, uuid,
} = useEditorConfigContext() } = useEditorConfigContext()
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { i18n, t } = useTranslation<object, 'lexical:link:loadingWithEllipsis'>() const { i18n, t } = useTranslation<object, 'lexical:link:loadingWithEllipsis'>()
@@ -150,7 +150,9 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
}`, }`,
) )
const relatedField = config.collections.find((coll) => coll.slug === fields?.doc?.relationTo) const relatedField = fields?.doc?.relationTo
? getEntityConfig({ collectionSlug: fields?.doc?.relationTo })
: undefined
if (!relatedField) { if (!relatedField) {
// Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all. // Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all.
// label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links. // label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links.

View File

@@ -59,14 +59,14 @@ const Component: React.FC<Props> = (props) => {
} = useEditorConfigContext() } = useEditorConfigContext()
const { const {
config: { config: {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const [relatedCollection, setRelatedCollection] = useState( const [relatedCollection, setRelatedCollection] = useState(() =>
() => collections.find((coll) => coll.slug === relationTo)!, getEntityConfig({ collectionSlug: relationTo }),
) )
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()

View File

@@ -64,10 +64,10 @@ const Component: React.FC<ElementProps> = (props) => {
const { const {
config: { config: {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const uploadRef = useRef<HTMLDivElement | null>(null) const uploadRef = useRef<HTMLDivElement | null>(null)
const { uuid } = useEditorConfigContext() const { uuid } = useEditorConfigContext()
@@ -82,8 +82,8 @@ const Component: React.FC<ElementProps> = (props) => {
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0) const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [relatedCollection] = useState<ClientCollectionConfig>( const [relatedCollection] = useState<ClientCollectionConfig>(() =>
() => collections.find((coll) => coll.slug === relationTo)!, getEntityConfig({ collectionSlug: relationTo }),
) )
const componentID = useId() const componentID = useId()

View File

@@ -69,7 +69,7 @@ export const LinkElement = () => {
const { id, collectionSlug, docPermissions, getDocPreferences, globalSlug } = useDocumentInfo() const { id, collectionSlug, docPermissions, getDocPreferences, globalSlug } = useDocumentInfo()
const editor = useSlate() const editor = useSlate()
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { code: locale } = useLocale() const { code: locale } = useLocale()
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const { closeModal, openModal, toggleModal } = useModal() const { closeModal, openModal, toggleModal } = useModal()
@@ -179,8 +179,7 @@ export const LinkElement = () => {
t={t} t={t}
variables={{ variables={{
label: getTranslation( label: getTranslation(
config.collections.find(({ slug }) => slug === element.doc.relationTo)?.labels getEntityConfig({ collectionSlug: element.doc.relationTo })?.labels?.singular,
?.singular,
i18n, i18n,
), ),
}} }}

View File

@@ -40,6 +40,7 @@ const RelationshipElementComponent: React.FC = () => {
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const [enabledCollectionSlugs] = useState(() => const [enabledCollectionSlugs] = useState(() =>
collections collections
@@ -47,7 +48,7 @@ const RelationshipElementComponent: React.FC = () => {
.map(({ slug }) => slug), .map(({ slug }) => slug),
) )
const [relatedCollection, setRelatedCollection] = useState(() => const [relatedCollection, setRelatedCollection] = useState(() =>
collections.find((coll) => coll.slug === relationTo), getEntityConfig({ collectionSlug: relationTo }),
) )
const selected = useSelected() const selected = useSelected()
@@ -117,7 +118,7 @@ const RelationshipElementComponent: React.FC = () => {
{ at: elementPath }, { at: elementPath },
) )
setRelatedCollection(collections.find((coll) => coll.slug === collectionSlug)) setRelatedCollection(getEntityConfig({ collectionSlug }))
setParams({ setParams({
...initialParams, ...initialParams,
@@ -127,7 +128,7 @@ const RelationshipElementComponent: React.FC = () => {
closeListDrawer() closeListDrawer()
dispatchCacheBust() dispatchCacheBust()
}, },
[closeListDrawer, editor, element, cacheBust, setParams, collections], [closeListDrawer, editor, element, cacheBust, setParams, getEntityConfig],
) )
return ( return (

View File

@@ -46,15 +46,15 @@ const UploadElementComponent: React.FC<{ enabledCollectionSlugs?: string[] }> =
const { const {
config: { config: {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
getEntityConfig,
} = useConfig() } = useConfig()
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0) const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [relatedCollection, setRelatedCollection] = useState<ClientCollectionConfig>(() => const [relatedCollection, setRelatedCollection] = useState<ClientCollectionConfig>(() =>
collections.find((coll) => coll.slug === relationTo), getEntityConfig({ collectionSlug: relationTo }),
) )
const drawerSlug = useDrawerSlug('upload-drawer') const drawerSlug = useDrawerSlug('upload-drawer')
@@ -121,14 +121,14 @@ const UploadElementComponent: React.FC<{ enabledCollectionSlugs?: string[] }> =
const elementPath = ReactEditor.findPath(editor, element) const elementPath = ReactEditor.findPath(editor, element)
setRelatedCollection(collections.find((coll) => coll.slug === collectionSlug)) setRelatedCollection(getEntityConfig({ collectionSlug }))
Transforms.setNodes(editor, newNode, { at: elementPath }) Transforms.setNodes(editor, newNode, { at: elementPath })
dispatchCacheBust() dispatchCacheBust()
closeListDrawer() closeListDrawer()
}, },
[closeListDrawer, editor, element, collections], [closeListDrawer, editor, element, getEntityConfig],
) )
const relatedFieldSchemaPath = `${uploadFieldsSchemaPath}.${relatedCollection.slug}` const relatedFieldSchemaPath = `${uploadFieldsSchemaPath}.${relatedCollection.slug}`

View File

@@ -6,14 +6,12 @@ import { useState } from 'react'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
export const useRelatedCollections = (relationTo: string | string[]): ClientCollectionConfig[] => { export const useRelatedCollections = (relationTo: string | string[]): ClientCollectionConfig[] => {
const { config } = useConfig() const { getEntityConfig } = useConfig()
const [relatedCollections] = useState(() => { const [relatedCollections] = useState(() => {
if (relationTo) { if (relationTo) {
const relations = typeof relationTo === 'string' ? [relationTo] : relationTo const relations = typeof relationTo === 'string' ? [relationTo] : relationTo
return relations.map((relation) => return relations.map((relation) => getEntityConfig({ collectionSlug: relation }))
config.collections.find((collection) => collection.slug === relation),
)
} }
return [] return []
}) })

View File

@@ -1,7 +1,5 @@
'use client' 'use client'
import type { ClientCollectionConfig } from 'payload'
import { useModal } from '@faceless-ui/modal' import { useModal } from '@faceless-ui/modal'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { reduceFieldsToValues } from 'payload/shared' import { reduceFieldsToValues } from 'payload/shared'
@@ -38,7 +36,7 @@ export function AddingFilesView() {
const { user } = useAuth() const { user } = useAuth()
const { openModal } = useModal() const { openModal } = useModal()
const collection = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collection = getEntityConfig({ collectionSlug })
return ( return (
<div className={baseClass}> <div className={baseClass}>

View File

@@ -58,7 +58,7 @@ export function EditForm({ submitted }: EditFormProps) {
const abortOnChangeRef = React.useRef<AbortController>(null) const abortOnChangeRef = React.useRef<AbortController>(null)
const collectionConfig = getEntityConfig({ collectionSlug: docSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug: docSlug })
const router = useRouter() const router = useRouter()
const depth = useEditDepth() const depth = useEditDepth()
const params = useSearchParams() const params = useSearchParams()

View File

@@ -20,10 +20,10 @@ function DrawerContent() {
const { addFiles, forms, isInitializing } = useFormsManager() const { addFiles, forms, isInitializing } = useFormsManager()
const { closeModal } = useModal() const { closeModal } = useModal()
const { collectionSlug, drawerSlug } = useBulkUpload() const { collectionSlug, drawerSlug } = useBulkUpload()
const { config } = useConfig() const { getEntityConfig } = useConfig()
const { t } = useTranslation() const { t } = useTranslation()
const uploadCollection = config.collections.find((col) => col.slug === collectionSlug) const uploadCollection = getEntityConfig({ collectionSlug })
const uploadConfig = uploadCollection?.upload const uploadConfig = uploadCollection?.upload
const uploadMimeTypes = uploadConfig?.mimeTypes const uploadMimeTypes = uploadConfig?.mimeTypes

View File

@@ -54,7 +54,7 @@ export const DeleteDocument: React.FC<Props> = (props) => {
getEntityConfig, getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const { setModified } = useForm() const { setModified } = useForm()
const [deleting, setDeleting] = useState(false) const [deleting, setDeleting] = useState(false)

View File

@@ -98,9 +98,9 @@ export const DocumentControls: React.FC<{
const { config, getEntityConfig } = useConfig() const { config, getEntityConfig } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug: slug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug: slug })
const globalConfig = getEntityConfig({ globalSlug: slug }) as ClientGlobalConfig const globalConfig = getEntityConfig({ globalSlug: slug })
const { const {
admin: { dateFormat }, admin: { dateFormat },

View File

@@ -30,14 +30,10 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
redirectAfterDelete, redirectAfterDelete,
redirectAfterDuplicate, redirectAfterDuplicate,
}) => { }) => {
const { const { getEntityConfig } = useConfig()
config: { collections },
} = useConfig()
const locale = useLocale() const locale = useLocale()
const [collectionConfig] = useState(() => const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug }))
collections.find((collection) => collection.slug === collectionSlug),
)
const abortGetDocumentViewRef = React.useRef<AbortController>(null) const abortGetDocumentViewRef = React.useRef<AbortController>(null)

View File

@@ -53,7 +53,7 @@ export const DuplicateDocument: React.FC<Props> = ({
getEntityConfig, getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug: slug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug: slug })
const [hasClicked, setHasClicked] = useState<boolean>(false) const [hasClicked, setHasClicked] = useState<boolean>(false)
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { ClientCollectionConfig, ListQuery } from 'payload' import type { ListQuery } from 'payload'
import { useModal } from '@faceless-ui/modal' import { useModal } from '@faceless-ui/modal'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
@@ -45,7 +45,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
const [selectedOption, setSelectedOption] = useState<Option<string>>(() => { const [selectedOption, setSelectedOption] = useState<Option<string>>(() => {
const initialSelection = selectedCollectionFromProps || enabledCollections[0]?.slug const initialSelection = selectedCollectionFromProps || enabledCollections[0]?.slug
const found = getEntityConfig({ collectionSlug: initialSelection }) as ClientCollectionConfig const found = getEntityConfig({ collectionSlug: initialSelection })
return found return found
? { ? {
@@ -63,7 +63,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
() => { () => {
if (selectedCollectionFromProps && selectedCollectionFromProps !== selectedOption?.value) { if (selectedCollectionFromProps && selectedCollectionFromProps !== selectedOption?.value) {
setSelectedOption({ setSelectedOption({
label: collections.find(({ slug }) => slug === selectedCollectionFromProps).labels, label: getEntityConfig({ collectionSlug: selectedCollectionFromProps })?.labels,
value: selectedCollectionFromProps, value: selectedCollectionFromProps,
}) })
} }

View File

@@ -18,8 +18,9 @@ export const Localizer: React.FC<{
className?: string className?: string
}> = (props) => { }> = (props) => {
const { className } = props const { className } = props
const { config } = useConfig() const {
const { localization } = config config: { localization },
} = useConfig()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const { setLocaleIsLoading } = useLocaleLoading() const { setLocaleIsLoading } = useLocaleLoading()

View File

@@ -31,7 +31,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
uploadStatus, uploadStatus,
} = useDocumentInfo() } = useDocumentInfo()
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { submit } = useForm() const { submit } = useForm()
const modified = useFormModified() const modified = useFormModified()
const editDepth = useEditDepth() const editDepth = useEditDepth()
@@ -51,13 +51,13 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
const entityConfig = React.useMemo(() => { const entityConfig = React.useMemo(() => {
if (collectionSlug) { if (collectionSlug) {
return config.collections.find(({ slug }) => slug === collectionSlug) return getEntityConfig({ collectionSlug })
} }
if (globalSlug) { if (globalSlug) {
return config.globals.find(({ slug }) => slug === globalSlug) return getEntityConfig({ globalSlug })
} }
}, [collectionSlug, globalSlug, config]) }, [collectionSlug, globalSlug, getEntityConfig])
const hasNewerVersions = unpublishedVersionCount > 0 const hasNewerVersions = unpublishedVersionCount > 0

View File

@@ -91,9 +91,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
const [query, setQuery] = useState<ListQuery>() const [query, setQuery] = useState<ListQuery>()
const [openColumnSelector, setOpenColumnSelector] = useState(false) const [openColumnSelector, setOpenColumnSelector] = useState(false)
const [collectionConfig] = useState( const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug: relationTo }))
() => getEntityConfig({ collectionSlug: relationTo }) as ClientCollectionConfig,
)
const [isLoadingTable, setIsLoadingTable] = useState(!disableTable) const [isLoadingTable, setIsLoadingTable] = useState(!disableTable)
const [data, setData] = useState<PaginatedDocs>(initialData) const [data, setData] = useState<PaginatedDocs>(initialData)

View File

@@ -1,6 +1,5 @@
'use client' 'use client'
import type { import type {
ClientCollectionConfig,
DefaultCellComponentProps, DefaultCellComponentProps,
JoinFieldClient, JoinFieldClient,
RelationshipFieldClient, RelationshipFieldClient,
@@ -98,7 +97,7 @@ export const RelationshipCell: React.FC<RelationshipCellProps> = ({
const document = documents[relationTo][value] const document = documents[relationTo][value]
const relatedCollection = getEntityConfig({ const relatedCollection = getEntityConfig({
collectionSlug: relationTo, collectionSlug: relationTo,
}) as ClientCollectionConfig })
const label = formatDocTitle({ const label = formatDocTitle({
collectionConfig: relatedCollection, collectionConfig: relatedCollection,

View File

@@ -67,7 +67,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
const { admin: { defaultColumns, useAsTitle } = {}, fields } = getEntityConfig({ const { admin: { defaultColumns, useAsTitle } = {}, fields } = getEntityConfig({
collectionSlug, collectionSlug,
}) as ClientCollectionConfig })
const prevCollection = React.useRef<SanitizedCollectionConfig['slug']>(collectionSlug) const prevCollection = React.useRef<SanitizedCollectionConfig['slug']>(collectionSlug)
const { getPreference } = usePreferences() const { getPreference } = usePreferences()

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { ClientCollectionConfig, PaginatedDocs, Where } from 'payload' import type { PaginatedDocs, Where } from 'payload'
import * as qs from 'qs-esm' import * as qs from 'qs-esm'
import React, { useCallback, useEffect, useReducer, useState } from 'react' import React, { useCallback, useEffect, useReducer, useState } from 'react'
@@ -28,7 +28,6 @@ export const RelationshipField: React.FC<Props> = (props) => {
const { const {
config: { config: {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
}, },
@@ -55,7 +54,7 @@ export const RelationshipField: React.FC<Props> = (props) => {
const addOptions = useCallback( const addOptions = useCallback(
(data, relation) => { (data, relation) => {
const collection = getEntityConfig({ collectionSlug: relation }) as ClientCollectionConfig const collection = getEntityConfig({ collectionSlug: relation })
dispatchOptions({ type: 'ADD', collection, data, hasMultipleRelations, i18n, relation }) dispatchOptions({ type: 'ADD', collection, data, hasMultipleRelations, i18n, relation })
}, },
[hasMultipleRelations, i18n, getEntityConfig], [hasMultipleRelations, i18n, getEntityConfig],
@@ -72,7 +71,7 @@ export const RelationshipField: React.FC<Props> = (props) => {
if (relationSlug && partiallyLoadedRelationshipSlugs.current.includes(relationSlug)) { if (relationSlug && partiallyLoadedRelationshipSlugs.current.includes(relationSlug)) {
const collection = getEntityConfig({ const collection = getEntityConfig({
collectionSlug: relationSlug, collectionSlug: relationSlug,
}) as ClientCollectionConfig })
const fieldToSearch = collection?.admin?.useAsTitle || 'id' const fieldToSearch = collection?.admin?.useAsTitle || 'id'
const pageIndex = nextPageByRelationshipRef.current.get(relationSlug) const pageIndex = nextPageByRelationshipRef.current.get(relationSlug)
@@ -135,7 +134,7 @@ export const RelationshipField: React.FC<Props> = (props) => {
setHasLoadedFirstOptions(true) setHasLoadedFirstOptions(true)
}, },
[addOptions, api, collections, debouncedSearch, i18n.language, serverURL, t], [addOptions, api, debouncedSearch, getEntityConfig, i18n.language, serverURL, t],
) )
const loadMoreOptions = React.useCallback(() => { const loadMoreOptions = React.useCallback(() => {

View File

@@ -127,9 +127,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
const { id: docID, docConfig } = useDocumentInfo() const { id: docID, docConfig } = useDocumentInfo()
const { const { getEntityConfig } = useConfig()
config: { collections },
} = useConfig()
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } = const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } =
useField<PaginatedDocs>({ useField<PaginatedDocs>({
@@ -166,7 +164,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
}, [docID, field.targetField.relationTo, field.where, on, docConfig.slug]) }, [docID, field.targetField.relationTo, field.where, on, docConfig.slug])
const initialDrawerData = useMemo(() => { const initialDrawerData = useMemo(() => {
const relatedCollection = collections.find((collection) => collection.slug === field.collection) const relatedCollection = getEntityConfig({ collectionSlug: field.collection })
return getInitialDrawerData({ return getInitialDrawerData({
collectionSlug: docConfig.slug, collectionSlug: docConfig.slug,
@@ -174,7 +172,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
fields: relatedCollection.fields, fields: relatedCollection.fields,
segments: field.on.split('.'), segments: field.on.split('.'),
}) })
}, [collections, field.on, field.collection, docConfig.slug, docID]) }, [getEntityConfig, field.collection, field.on, docConfig.slug, docID])
return ( return (
<div <div

View File

@@ -60,10 +60,9 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
validate, validate,
} = props } = props
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { const {
collections,
routes: { api }, routes: { api },
serverURL, serverURL,
} = config } = config
@@ -169,7 +168,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
} }
if (resultsFetched < 10) { if (resultsFetched < 10) {
const collection = collections.find((coll) => coll.slug === relation) const collection = getEntityConfig({ collectionSlug: relation })
const fieldToSearch = collection?.admin?.useAsTitle || 'id' const fieldToSearch = collection?.admin?.useAsTitle || 'id'
let fieldToSort = collection?.defaultSort || 'id' let fieldToSort = collection?.defaultSort || 'id'
if (typeof sortOptions === 'string') { if (typeof sortOptions === 'string') {
@@ -275,7 +274,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
hasMany, hasMany,
errorLoading, errorLoading,
search, search,
collections, getEntityConfig,
locale, locale,
serverURL, serverURL,
sortOptions, sortOptions,
@@ -354,7 +353,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
method: 'POST', method: 'POST',
}) })
const collection = collections.find((coll) => coll.slug === relation) const collection = getEntityConfig({ collectionSlug: relation })
let docs = [] let docs = []
if (response.ok) { if (response.ok) {
@@ -380,7 +379,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
options, options,
hasMany, hasMany,
errorLoading, errorLoading,
collections, getEntityConfig,
hasMultipleRelations, hasMultipleRelations,
serverURL, serverURL,
api, api,
@@ -395,12 +394,12 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
useEffect(() => { useEffect(() => {
const relations = Array.isArray(relationTo) ? relationTo : [relationTo] const relations = Array.isArray(relationTo) ? relationTo : [relationTo]
const isIdOnly = relations.reduce((idOnly, relation) => { const isIdOnly = relations.reduce((idOnly, relation) => {
const collection = collections.find((coll) => coll.slug === relation) const collection = getEntityConfig({ collectionSlug: relation })
const fieldToSearch = collection?.admin?.useAsTitle || 'id' const fieldToSearch = collection?.admin?.useAsTitle || 'id'
return fieldToSearch === 'id' && idOnly return fieldToSearch === 'id' && idOnly
}, true) }, true)
setEnableWordBoundarySearch(!isIdOnly) setEnableWordBoundarySearch(!isIdOnly)
}, [relationTo, collections]) }, [relationTo, getEntityConfig])
// When (`relationTo` || `filterOptions` || `locale`) changes, reset component // When (`relationTo` || `filterOptions` || `locale`) changes, reset component
// Note - effect should not run on first run // Note - effect should not run on first run

View File

@@ -1,26 +1,42 @@
/* eslint-disable perfectionist/sort-object-types */ // Need to disable this rule because the order of the overloads is important
'use client' 'use client'
import type { ClientCollectionConfig, ClientConfig, ClientGlobalConfig } from 'payload' import type {
ClientCollectionConfig,
ClientConfig,
ClientGlobalConfig,
CollectionSlug,
GlobalSlug,
} from 'payload'
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react' import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
type GetEntityConfigFn = {
// Overload #1: collectionSlug only
// @todo remove "{} |" in 4.0, which would be a breaking change
(args: { collectionSlug: {} | CollectionSlug; globalSlug?: never }): ClientCollectionConfig
// Overload #2: globalSlug only
// @todo remove "{} |" in 4.0, which would be a breaking change
(args: { collectionSlug?: never; globalSlug: {} | GlobalSlug }): ClientGlobalConfig
// Overload #3: both/none (fall back to union | null)
(args: {
collectionSlug?: {} | CollectionSlug
globalSlug?: {} | GlobalSlug
}): ClientCollectionConfig | ClientGlobalConfig | null
}
export type ClientConfigContext = { export type ClientConfigContext = {
config: ClientConfig config: ClientConfig
getEntityConfig: (args: { /**
collectionSlug?: string * Get a collection or global config by its slug. This is preferred over
globalSlug?: string * using `config.collections.find` or `config.globals.find`, because
}) => ClientCollectionConfig | ClientGlobalConfig | null * getEntityConfig uses a lookup map for O(1) lookups.
*/
getEntityConfig: GetEntityConfigFn
setConfig: (config: ClientConfig) => void setConfig: (config: ClientConfig) => void
} }
export type EntityConfigContext = {
collectionConfig?: ClientCollectionConfig
globalConfig?: ClientGlobalConfig
setEntityConfig: (args: {
collectionConfig?: ClientCollectionConfig | null
globalConfig?: ClientGlobalConfig | null
}) => void
}
const RootConfigContext = createContext<ClientConfigContext | undefined>(undefined) const RootConfigContext = createContext<ClientConfigContext | undefined>(undefined)
export const ConfigProvider: React.FC<{ export const ConfigProvider: React.FC<{
@@ -35,19 +51,32 @@ export const ConfigProvider: React.FC<{
setConfig(configFromProps) setConfig(configFromProps)
}, [configFromProps]) }, [configFromProps])
const getEntityConfig = useCallback( // Build lookup maps for collections and globals so we can do O(1) lookups by slug
({ collectionSlug, globalSlug }: { collectionSlug?: string; globalSlug?: string }) => { const { collectionsBySlug, globalsBySlug } = useMemo(() => {
if (collectionSlug) { const collectionsBySlug: Record<string, ClientCollectionConfig> = {}
return config.collections.find((collection) => collection.slug === collectionSlug) const globalsBySlug: Record<string, ClientGlobalConfig> = {}
}
if (globalSlug) { for (const collection of config.collections) {
return config.globals.find((global) => global.slug === globalSlug) collectionsBySlug[collection.slug] = collection
} }
for (const global of config.globals) {
globalsBySlug[global.slug] = global
}
return null return { collectionsBySlug, globalsBySlug }
}, [config])
const getEntityConfig = useCallback<GetEntityConfigFn>(
(args) => {
if ('collectionSlug' in args) {
return collectionsBySlug[args.collectionSlug] ?? null
}
if ('globalSlug' in args) {
return globalsBySlug[args.globalSlug] ?? null
}
return null as any
}, },
[config], [collectionsBySlug, globalsBySlug],
) )
return ( return (

View File

@@ -1,11 +1,5 @@
'use client' 'use client'
import type { import type { ClientUser, DocumentPreferences, SanitizedDocumentPermissions } from 'payload'
ClientCollectionConfig,
ClientGlobalConfig,
ClientUser,
DocumentPreferences,
SanitizedDocumentPermissions,
} from 'payload'
import * as qs from 'qs-esm' import * as qs from 'qs-esm'
import React, { import React, {
@@ -79,8 +73,8 @@ const DocumentInfo: React.FC<
getEntityConfig, getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig const globalConfig = getEntityConfig({ globalSlug })
const abortControllerRef = useRef(new AbortController()) const abortControllerRef = useRef(new AbortController())
const docConfig = collectionConfig || globalConfig const docConfig = collectionConfig || globalConfig

View File

@@ -14,6 +14,8 @@ type Result = {
user: TypedUser user: TypedUser
} }
const lockDurationDefault = 300 // Default 5 minutes in seconds
export const handleFormStateLocking = async ({ export const handleFormStateLocking = async ({
id, id,
collectionSlug, collectionSlug,
@@ -39,9 +41,8 @@ export const handleFormStateLocking = async ({
} }
} }
const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDocumentsProp = collectionSlug const lockDocumentsProp = collectionSlug
? req.payload.config.collections.find((c) => c.slug === collectionSlug)?.lockDocuments ? req.payload.collections?.[collectionSlug]?.config.lockDocuments
: req.payload.config.globals.find((g) => g.slug === globalSlug)?.lockDocuments : req.payload.config.globals.find((g) => g.slug === globalSlug)?.lockDocuments
const lockDuration = const lockDuration =

View File

@@ -8,7 +8,6 @@ import { v4 as uuidv4 } from 'uuid'
import { CopyToClipboard } from '../../../elements/CopyToClipboard/index.js' import { CopyToClipboard } from '../../../elements/CopyToClipboard/index.js'
import { GenerateConfirmation } from '../../../elements/GenerateConfirmation/index.js' import { GenerateConfirmation } from '../../../elements/GenerateConfirmation/index.js'
import { FieldLabel } from '../../../fields/FieldLabel/index.js'
import { useFormFields } from '../../../forms/Form/context.js' import { useFormFields } from '../../../forms/Form/context.js'
import { useField } from '../../../forms/useField/index.js' import { useField } from '../../../forms/useField/index.js'
import { useConfig } from '../../../providers/Config/index.js' import { useConfig } from '../../../providers/Config/index.js'
@@ -26,16 +25,14 @@ export const APIKey: React.FC<{ readonly enabled: boolean; readonly readOnly?: b
const [initialAPIKey] = useState(uuidv4()) const [initialAPIKey] = useState(uuidv4())
const [highlightedField, setHighlightedField] = useState(false) const [highlightedField, setHighlightedField] = useState(false)
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const { config } = useConfig() const { config, getEntityConfig } = useConfig()
const { collectionSlug } = useDocumentInfo() const { collectionSlug } = useDocumentInfo()
const apiKey = useFormFields(([fields]) => (fields && fields[path]) || null) const apiKey = useFormFields(([fields]) => (fields && fields[path]) || null)
const apiKeyField: TextFieldClient = config.collections const apiKeyField: TextFieldClient = getEntityConfig({ collectionSlug })?.fields?.find(
.find((collection) => { (field) => 'name' in field && field.name === 'apiKey',
return collection.slug === collectionSlug ) as TextFieldClient
})
?.fields?.find((field) => 'name' in field && field.name === 'apiKey') as TextFieldClient
const validate = (val) => const validate = (val) =>
text(val, { text(val, {

View File

@@ -1,12 +1,6 @@
'use client' 'use client'
import type { import type { ClientSideEditViewProps, ClientUser, FormState } from 'payload'
ClientCollectionConfig,
ClientGlobalConfig,
ClientSideEditViewProps,
ClientUser,
FormState,
} from 'payload'
import { useRouter, useSearchParams } from 'next/navigation.js' import { useRouter, useSearchParams } from 'next/navigation.js'
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -109,8 +103,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
getEntityConfig, getEntityConfig,
} = useConfig() } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig const globalConfig = getEntityConfig({ globalSlug })
const depth = useEditDepth() const depth = useEditDepth()

View File

@@ -90,6 +90,7 @@ const ListDrawerHeader: React.FC<ListHeaderProps> = ({
}) => { }) => {
const { const {
config: { collections }, config: { collections },
getEntityConfig,
} = useConfig() } = useConfig()
const { closeModal } = useModal() const { closeModal } = useModal()
@@ -102,7 +103,7 @@ const ListDrawerHeader: React.FC<ListHeaderProps> = ({
setSelectedOption, setSelectedOption,
} = useListDrawerContext() } = useListDrawerContext()
const collectionConfig = collections.find(({ slug }) => slug === selectedOption.value) const collectionConfig = getEntityConfig({ collectionSlug: selectedOption.value })
const enabledCollectionConfigs = collections.filter(({ slug }) => const enabledCollectionConfigs = collections.filter(({ slug }) =>
enabledCollections.includes(slug), enabledCollections.includes(slug),

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import type { ClientCollectionConfig, ListPreferences } from 'payload' import type { ListPreferences } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import LinkImport from 'next/link.js' import LinkImport from 'next/link.js'
@@ -113,7 +113,7 @@ export const DefaultListView: React.FC<ListViewClientProps> = (props) => {
const { setCollectionSlug, setCurrentActivePath, setOnSuccess } = useBulkUpload() const { setCollectionSlug, setCurrentActivePath, setOnSuccess } = useBulkUpload()
const { drawerSlug: bulkUploadDrawerSlug } = useBulkUpload() const { drawerSlug: bulkUploadDrawerSlug } = useBulkUpload()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig const collectionConfig = getEntityConfig({ collectionSlug })
const { labels, upload } = collectionConfig const { labels, upload } = collectionConfig