Skip to content

Commit

Permalink
fix(richtext-*): ensure admin panel doesn't freeze with some field co…
Browse files Browse the repository at this point in the history
…nfigurations 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
  • Loading branch information
AlessioGr authored Oct 18, 2024
1 parent 9056b9f commit aedf3c8
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 3 deletions.
27 changes: 24 additions & 3 deletions packages/ui/src/providers/Config/createClientConfig/fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
LabelsClient,
MappedComponent,
Payload,
PayloadComponent,
RadioFieldClient,
RichTextFieldClient,
RichTextGenerateComponentMap,
Expand All @@ -25,7 +26,7 @@ import type {
TabsFieldClient,
} from 'payload'

import { MissingEditorProp } from 'payload'
import { deepCopyObjectSimple, MissingEditorProp } from 'payload'
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'

import { getComponent } from './getComponent.js'
Expand Down Expand Up @@ -247,15 +248,35 @@ export const createClientField = ({
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(
incomingField.editor.FieldComponent,
FieldComponent,
serverProps,
undefined,
'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(
incomingField.editor.CellComponent,
CellComponent,
serverProps,
undefined,
'incomingField.editor.CellComponent',
Expand Down
8 changes: 8 additions & 0 deletions test/fields/collections/Lexical/e2e/main/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,14 @@ describe('lexicalMain', () => {
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', () => {
test.skip('ensure simple localized lexical field works', async () => {
await navigateToLexicalFields(true, true)
Expand Down
38 changes: 38 additions & 0 deletions test/fields/collections/LexicalObjectReferenceBug/index.ts
Original file line number Diff line number Diff line change
@@ -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',
},
],
},
},
}),
],
}),
},
],
}
2 changes: 2 additions & 0 deletions test/fields/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import JSONFields from './collections/JSON/index.js'
import { LexicalFields } from './collections/Lexical/index.js'
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
import { LexicalObjectReferenceBugCollection } from './collections/LexicalObjectReferenceBug/index.js'
import { LexicalRelationshipsFields } from './collections/LexicalRelationships/index.js'
import NumberFields from './collections/Number/index.js'
import PointFields from './collections/Point/index.js'
Expand All @@ -46,6 +47,7 @@ export const collectionSlugs: CollectionConfig[] = [
LexicalFields,
LexicalMigrateFields,
LexicalLocalizedFields,
LexicalObjectReferenceBugCollection,
{
slug: 'users',
admin: {
Expand Down

0 comments on commit aedf3c8

Please sign in to comment.