Skip to content

Commit aedf3c8

Browse files
authored
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
1 parent 9056b9f commit aedf3c8

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-3
lines changed

packages/ui/src/providers/Config/createClientConfig/fields.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
LabelsClient,
1414
MappedComponent,
1515
Payload,
16+
PayloadComponent,
1617
RadioFieldClient,
1718
RichTextFieldClient,
1819
RichTextGenerateComponentMap,
@@ -25,7 +26,7 @@ import type {
2526
TabsFieldClient,
2627
} from 'payload'
2728

28-
import { MissingEditorProp } from 'payload'
29+
import { deepCopyObjectSimple, MissingEditorProp } from 'payload'
2930
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
3031

3132
import { getComponent } from './getComponent.js'
@@ -247,15 +248,35 @@ export const createClientField = ({
247248
field.admin.components = {}
248249
}
249250

251+
/**
252+
* We have to deep copy all the props we send to the client (= FieldComponent.clientProps).
253+
* That way, every editor's field / cell props we send to the client have their own object references.
254+
*
255+
* If we send the same object reference to the client twice (e.g. through some configurations where 2 or more fields
256+
* reference the same editor object, like the root editor), the admin panel may hang indefinitely. This has been happening since
257+
* a newer Next.js update that made it break when sending the same object reference to the client twice.
258+
*
259+
* We can use deepCopyObjectSimple as client props should be JSON-serializable.
260+
*/
261+
const FieldComponent: PayloadComponent = incomingField.editor.FieldComponent
262+
if (typeof FieldComponent === 'object' && FieldComponent.clientProps) {
263+
FieldComponent.clientProps = deepCopyObjectSimple(FieldComponent.clientProps)
264+
}
265+
250266
field.admin.components.Field = createMappedComponent(
251-
incomingField.editor.FieldComponent,
267+
FieldComponent,
252268
serverProps,
253269
undefined,
254270
'incomingField.editor.FieldComponent',
255271
)
256272

273+
const CellComponent: PayloadComponent = incomingField.editor.CellComponent
274+
if (typeof CellComponent === 'object' && CellComponent.clientProps) {
275+
CellComponent.clientProps = deepCopyObjectSimple(CellComponent.clientProps)
276+
}
277+
257278
field.admin.components.Cell = createMappedComponent(
258-
incomingField.editor.CellComponent,
279+
CellComponent,
259280
serverProps,
260281
undefined,
261282
'incomingField.editor.CellComponent',

test/fields/collections/Lexical/e2e/main/e2e.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,14 @@ describe('lexicalMain', () => {
553553
await expect(relationshipListDrawer).toHaveText('Array Fields')
554554
})
555555

556+
test('ensure navigation to collection that used to cause admin panel freeze due to object references bug is possible', async () => {
557+
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'lexicalObjectReferenceBug')
558+
await page.goto(url.create)
559+
560+
await expect(page.locator('.rich-text-lexical').nth(0)).toBeVisible()
561+
await expect(page.locator('.rich-text-lexical').nth(1)).toBeVisible()
562+
})
563+
556564
describe('localization', () => {
557565
test.skip('ensure simple localized lexical field works', async () => {
558566
await navigateToLexicalFields(true, true)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { lexicalEditor, UploadFeature } from '@payloadcms/richtext-lexical'
4+
5+
/**
6+
* Do not change this specific CollectionConfig. Simply having this config in payload used to cause the admin panel to hang.
7+
* Thus, simply having this config in the test suite is enough to test the bug fix and prevent regressions. In case of regression,
8+
* the entire admin panel will hang again and all tests will fail.
9+
*/
10+
export const LexicalObjectReferenceBugCollection: CollectionConfig = {
11+
slug: 'lexicalObjectReferenceBug',
12+
fields: [
13+
{
14+
name: 'lexicalDefault',
15+
type: 'richText',
16+
},
17+
{
18+
name: 'lexicalEditor',
19+
type: 'richText',
20+
editor: lexicalEditor({
21+
features: [
22+
UploadFeature({
23+
collections: {
24+
media: {
25+
fields: [
26+
{
27+
name: 'caption',
28+
type: 'richText',
29+
},
30+
],
31+
},
32+
},
33+
}),
34+
],
35+
}),
36+
},
37+
],
38+
}

test/fields/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import JSONFields from './collections/JSON/index.js'
2020
import { LexicalFields } from './collections/Lexical/index.js'
2121
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
2222
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
23+
import { LexicalObjectReferenceBugCollection } from './collections/LexicalObjectReferenceBug/index.js'
2324
import { LexicalRelationshipsFields } from './collections/LexicalRelationships/index.js'
2425
import NumberFields from './collections/Number/index.js'
2526
import PointFields from './collections/Point/index.js'
@@ -46,6 +47,7 @@ export const collectionSlugs: CollectionConfig[] = [
4647
LexicalFields,
4748
LexicalMigrateFields,
4849
LexicalLocalizedFields,
50+
LexicalObjectReferenceBugCollection,
4951
{
5052
slug: 'users',
5153
admin: {

0 commit comments

Comments
 (0)