perf(ui): use select API in RelationshipProvider to speed up load times in RelationshipCell (#13832)

### What?
Optimize the `RelationshipProvider` to only select the `useAsTitle`
field when fetching documents via the REST API. This reduces payload
size and speeds up loading of the related document title in
the`RelationshipCell` in the table view.

### Why?
Previously, when a document had a relationship field, the full document
data was requested in the table view, even though the relationship cell
only shows the title in the UI. On large collections, this caused
unnecessary overhead and slower UI performance.

### How?
Applies a select to the REST API request made in the
`RelationshipProvider`, limiting the responses to the `useAsTitle` field
only.

### Notes
- I’m not entirely sure whether this introduces a breaking change. If it
does, could you suggest a way to make this behavior opt-in?
- For upload enabled collections, the full document must be requested,
because the relationship cell needs access to fields like `mimeType`,
`thumbailURL` etc.
- I hope we can find a way to get this merged. In the Payload projects I
work on, this change has significantly improved list view performance.


Similar to #13228
This commit is contained in:
Jens Becker
2025-09-19 17:20:55 +02:00
committed by GitHub
parent 22ae9fa9d0
commit 77cac30046
4 changed files with 23 additions and 10 deletions

View File

@@ -16,6 +16,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
import { getColumns, renderFilters, renderTable, upsertPreferences } from '@payloadcms/ui/rsc'
import { notFound } from 'next/navigation.js'
import {
appendUploadSelectFields,
combineWhereConstraints,
formatAdminURL,
isNumber,
@@ -26,7 +27,6 @@ import {
import React, { Fragment } from 'react'
import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
import { appendUploadSelectFields } from './appendUploadSelectFields.js'
import { handleGroupBy } from './handleGroupBy.js'
import { renderListViewSlots } from './renderListViewSlots.js'
import { resolveAllFilterOptions } from './resolveAllFilterOptions.js'

View File

@@ -58,6 +58,7 @@ export { validOperators, validOperatorSet } from '../types/constants.js'
export { formatFilesize } from '../uploads/formatFilesize.js'
export { isImage } from '../uploads/isImage.js'
export { appendUploadSelectFields } from '../utilities/appendUploadSelectFields.js'
export { combineWhereConstraints } from '../utilities/combineWhereConstraints.js'
export {

View File

@@ -1,4 +1,4 @@
import type { SanitizedCollectionConfig, SelectType } from 'payload'
import type { ClientCollectionConfig, SanitizedCollectionConfig, SelectType } from '../index.js'
/**
* Mutates the incoming select object to append fields required for upload thumbnails
@@ -9,7 +9,7 @@ export const appendUploadSelectFields = ({
collectionConfig,
select,
}: {
collectionConfig: SanitizedCollectionConfig
collectionConfig: ClientCollectionConfig | SanitizedCollectionConfig
select: SelectType
}) => {
if (!collectionConfig.upload || !select) {

View File

@@ -1,6 +1,8 @@
'use client'
import type { TypeWithID } from 'payload'
import type { SelectType, TypeWithID } from 'payload'
import { appendUploadSelectFields } from 'payload/shared'
import * as qs from 'qs-esm'
import React, { createContext, use, useCallback, useEffect, useReducer, useRef } from 'react'
import { useDebounce } from '../../../hooks/useDebounce.js'
@@ -38,6 +40,7 @@ export const RelationshipProvider: React.FC<{ readonly children?: React.ReactNod
const {
config: {
collections,
routes: { api },
serverURL,
},
@@ -62,20 +65,29 @@ export const RelationshipProvider: React.FC<{ readonly children?: React.ReactNod
const url = `${serverURL}${api}/${slug}`
const params = new URLSearchParams()
const select: SelectType = {}
params.append('depth', '0')
params.append('limit', '250')
const collection = collections.find((c) => c.slug === slug)
if (collection.admin.enableListViewSelectAPI) {
const fieldToSelect = collection.admin.useAsTitle ?? 'id'
select[fieldToSelect] = true
if (collection.upload) {
appendUploadSelectFields({ collectionConfig: collection, select })
}
}
if (locale) {
params.append('locale', locale)
}
if (idsToLoad && idsToLoad.length > 0) {
const idsToString = idsToLoad.map((id) => String(id))
params.append('where[id][in]', idsToString.join(','))
}
const idsToString = idsToLoad.map((id) => String(id))
params.append('where[id][in]', idsToString.join(','))
const query = `?${params.toString()}`
const query = `?${params.toString()}&${qs.stringify({ select })}`
const result = await fetch(`${url}${query}`, {
credentials: 'include',
@@ -100,7 +112,7 @@ export const RelationshipProvider: React.FC<{ readonly children?: React.ReactNod
}
})
},
[debouncedDocuments, serverURL, api, i18n, locale],
[debouncedDocuments, serverURL, api, i18n, locale, collections],
)
useEffect(() => {