fix(richtext-*): ensure admin panel doesn't freeze with some field configurations consisting of 2+ richtext fields (#8773)

See comments in code for proper explanation. In some cases, where 2
richtext `editor`s referencing the same `editor` are used, the admin
panel will hang. That's because the server will send their client props
that have the same object reference down to the client twice.

Next.js sometimes does not like this and, ever since one of the v15
canaries, started to hang
This commit is contained in:
Alessio Gravili
2024-10-17 21:22:05 -06:00
committed by GitHub
parent 9056b9fe9b
commit aedf3c8a76
4 changed files with 72 additions and 3 deletions

View File

@@ -13,6 +13,7 @@ import type {
LabelsClient, LabelsClient,
MappedComponent, MappedComponent,
Payload, Payload,
PayloadComponent,
RadioFieldClient, RadioFieldClient,
RichTextFieldClient, RichTextFieldClient,
RichTextGenerateComponentMap, RichTextGenerateComponentMap,
@@ -25,7 +26,7 @@ import type {
TabsFieldClient, TabsFieldClient,
} from 'payload' } from 'payload'
import { MissingEditorProp } from 'payload' import { deepCopyObjectSimple, MissingEditorProp } from 'payload'
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared' import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
import { getComponent } from './getComponent.js' import { getComponent } from './getComponent.js'
@@ -247,15 +248,35 @@ export const createClientField = ({
field.admin.components = {} field.admin.components = {}
} }
/**
* We have to deep copy all the props we send to the client (= FieldComponent.clientProps).
* That way, every editor's field / cell props we send to the client have their own object references.
*
* If we send the same object reference to the client twice (e.g. through some configurations where 2 or more fields
* reference the same editor object, like the root editor), the admin panel may hang indefinitely. This has been happening since
* a newer Next.js update that made it break when sending the same object reference to the client twice.
*
* We can use deepCopyObjectSimple as client props should be JSON-serializable.
*/
const FieldComponent: PayloadComponent = incomingField.editor.FieldComponent
if (typeof FieldComponent === 'object' && FieldComponent.clientProps) {
FieldComponent.clientProps = deepCopyObjectSimple(FieldComponent.clientProps)
}
field.admin.components.Field = createMappedComponent( field.admin.components.Field = createMappedComponent(
incomingField.editor.FieldComponent, FieldComponent,
serverProps, serverProps,
undefined, undefined,
'incomingField.editor.FieldComponent', 'incomingField.editor.FieldComponent',
) )
const CellComponent: PayloadComponent = incomingField.editor.CellComponent
if (typeof CellComponent === 'object' && CellComponent.clientProps) {
CellComponent.clientProps = deepCopyObjectSimple(CellComponent.clientProps)
}
field.admin.components.Cell = createMappedComponent( field.admin.components.Cell = createMappedComponent(
incomingField.editor.CellComponent, CellComponent,
serverProps, serverProps,
undefined, undefined,
'incomingField.editor.CellComponent', 'incomingField.editor.CellComponent',

View File

@@ -553,6 +553,14 @@ describe('lexicalMain', () => {
await expect(relationshipListDrawer).toHaveText('Array Fields') await expect(relationshipListDrawer).toHaveText('Array Fields')
}) })
test('ensure navigation to collection that used to cause admin panel freeze due to object references bug is possible', async () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'lexicalObjectReferenceBug')
await page.goto(url.create)
await expect(page.locator('.rich-text-lexical').nth(0)).toBeVisible()
await expect(page.locator('.rich-text-lexical').nth(1)).toBeVisible()
})
describe('localization', () => { describe('localization', () => {
test.skip('ensure simple localized lexical field works', async () => { test.skip('ensure simple localized lexical field works', async () => {
await navigateToLexicalFields(true, true) await navigateToLexicalFields(true, true)

View File

@@ -0,0 +1,38 @@
import type { CollectionConfig } from 'payload'
import { lexicalEditor, UploadFeature } from '@payloadcms/richtext-lexical'
/**
* Do not change this specific CollectionConfig. Simply having this config in payload used to cause the admin panel to hang.
* Thus, simply having this config in the test suite is enough to test the bug fix and prevent regressions. In case of regression,
* the entire admin panel will hang again and all tests will fail.
*/
export const LexicalObjectReferenceBugCollection: CollectionConfig = {
slug: 'lexicalObjectReferenceBug',
fields: [
{
name: 'lexicalDefault',
type: 'richText',
},
{
name: 'lexicalEditor',
type: 'richText',
editor: lexicalEditor({
features: [
UploadFeature({
collections: {
media: {
fields: [
{
name: 'caption',
type: 'richText',
},
],
},
},
}),
],
}),
},
],
}

View File

@@ -20,6 +20,7 @@ import JSONFields from './collections/JSON/index.js'
import { LexicalFields } from './collections/Lexical/index.js' import { LexicalFields } from './collections/Lexical/index.js'
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js' import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js' import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
import { LexicalObjectReferenceBugCollection } from './collections/LexicalObjectReferenceBug/index.js'
import { LexicalRelationshipsFields } from './collections/LexicalRelationships/index.js' import { LexicalRelationshipsFields } from './collections/LexicalRelationships/index.js'
import NumberFields from './collections/Number/index.js' import NumberFields from './collections/Number/index.js'
import PointFields from './collections/Point/index.js' import PointFields from './collections/Point/index.js'
@@ -46,6 +47,7 @@ export const collectionSlugs: CollectionConfig[] = [
LexicalFields, LexicalFields,
LexicalMigrateFields, LexicalMigrateFields,
LexicalLocalizedFields, LexicalLocalizedFields,
LexicalObjectReferenceBugCollection,
{ {
slug: 'users', slug: 'users',
admin: { admin: {