fix(ui): stale server components when rows are moved (#9410)

We need to trigger server component re-rendering for moving rows, just
like we do for adding or deleting rows.

Video of the issue:


https://github.com/user-attachments/assets/32fb31c5-f304-4082-8028-59a6ad723fbe
This commit is contained in:
Alessio Gravili
2024-11-21 13:14:57 -07:00
committed by GitHub
parent f338c5c40c
commit e3866c4035
6 changed files with 131 additions and 13 deletions

View File

@@ -198,6 +198,7 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
...flattenRows(path, rows), ...flattenRows(path, rows),
[path]: { [path]: {
...state[path], ...state[path],
requiresRender: true,
rows: rowStateCopy, rows: rowStateCopy,
}, },
} }

View File

@@ -1044,6 +1044,30 @@ describe('lexicalMain', () => {
await expect(htmlOutput).toBeVisible() await expect(htmlOutput).toBeVisible()
}) })
test('ensure lexical fields in blocks have correct value when moving blocks', async () => {
// Previously, we had the issue that the lexical field values did not update when moving blocks, as the MOVE_ROW form action did not request
// re-rendering of server components
await page.goto('http://localhost:3000/admin/collections/LexicalInBlock?limit=10')
await page.locator('.cell-id a').first().click()
await page.waitForURL(`**/collections/LexicalInBlock/**`)
await expect(page.locator('#blocks-row-0 .LexicalEditorTheme__paragraph')).toContainText('1')
await expect(page.locator('#blocks-row-0 .section-title__input')).toHaveValue('1') // block name
await expect(page.locator('#blocks-row-1 .LexicalEditorTheme__paragraph')).toContainText('2')
await expect(page.locator('#blocks-row-1 .section-title__input')).toHaveValue('2') // block name
// Move block 1 to the end
await page.locator('#blocks-row-0 .array-actions__button').click()
await expect(page.locator('#blocks-row-0 .popup__content')).toBeVisible()
await page.locator('#blocks-row-0 .popup__content').getByText('Move Down').click()
await expect(page.locator('#blocks-row-0 .LexicalEditorTheme__paragraph')).toContainText('2')
await expect(page.locator('#blocks-row-0 .section-title__input')).toHaveValue('2') // block name
await expect(page.locator('#blocks-row-1 .LexicalEditorTheme__paragraph')).toContainText('1')
await expect(page.locator('#blocks-row-1 .section-title__input')).toHaveValue('1') // block name
})
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,22 @@
import type { CollectionConfig } from 'payload'
export const LexicalInBlock: CollectionConfig = {
slug: 'LexicalInBlock',
fields: [
{
name: 'blocks',
type: 'blocks',
blocks: [
{
slug: 'lexicalInBlock2',
fields: [
{
name: 'lexical',
type: 'richText',
},
],
},
],
},
],
}

View File

@@ -19,6 +19,7 @@ import GroupFields from './collections/Group/index.js'
import IndexedFields from './collections/Indexed/index.js' import IndexedFields from './collections/Indexed/index.js'
import JSONFields from './collections/JSON/index.js' import JSONFields from './collections/JSON/index.js'
import { LexicalFields } from './collections/Lexical/index.js' import { LexicalFields } from './collections/Lexical/index.js'
import { LexicalInBlock } from './collections/LexicalInBlock/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 { LexicalObjectReferenceBugCollection } from './collections/LexicalObjectReferenceBug/index.js'
@@ -63,6 +64,8 @@ export const collectionSlugs: CollectionConfig[] = [
}, },
], ],
}, },
LexicalInBlock,
ArrayFields, ArrayFields,
BlockFields, BlockFields,
CheckboxFields, CheckboxFields,

View File

@@ -33,6 +33,7 @@ export interface Config {
'lexical-localized-fields': LexicalLocalizedField; 'lexical-localized-fields': LexicalLocalizedField;
lexicalObjectReferenceBug: LexicalObjectReferenceBug; lexicalObjectReferenceBug: LexicalObjectReferenceBug;
users: User; users: User;
LexicalInBlock: LexicalInBlock;
'array-fields': ArrayField; 'array-fields': ArrayField;
'block-fields': BlockField; 'block-fields': BlockField;
'checkbox-fields': CheckboxField; 'checkbox-fields': CheckboxField;
@@ -75,6 +76,7 @@ export interface Config {
'lexical-localized-fields': LexicalLocalizedFieldsSelect<false> | LexicalLocalizedFieldsSelect<true>; 'lexical-localized-fields': LexicalLocalizedFieldsSelect<false> | LexicalLocalizedFieldsSelect<true>;
lexicalObjectReferenceBug: LexicalObjectReferenceBugSelect<false> | LexicalObjectReferenceBugSelect<true>; lexicalObjectReferenceBug: LexicalObjectReferenceBugSelect<false> | LexicalObjectReferenceBugSelect<true>;
users: UsersSelect<false> | UsersSelect<true>; users: UsersSelect<false> | UsersSelect<true>;
LexicalInBlock: LexicalInBlockSelect<false> | LexicalInBlockSelect<true>;
'array-fields': ArrayFieldsSelect<false> | ArrayFieldsSelect<true>; 'array-fields': ArrayFieldsSelect<false> | ArrayFieldsSelect<true>;
'block-fields': BlockFieldsSelect<false> | BlockFieldsSelect<true>; 'block-fields': BlockFieldsSelect<false> | BlockFieldsSelect<true>;
'checkbox-fields': CheckboxFieldsSelect<false> | CheckboxFieldsSelect<true>; 'checkbox-fields': CheckboxFieldsSelect<false> | CheckboxFieldsSelect<true>;
@@ -123,9 +125,9 @@ export interface Config {
user: User & { user: User & {
collection: 'users'; collection: 'users';
}; };
jobs?: { jobs: {
tasks: unknown; tasks: unknown;
workflows?: unknown; workflows: unknown;
}; };
} }
export interface UserAuthOperations { export interface UserAuthOperations {
@@ -394,6 +396,37 @@ export interface User {
lockUntil?: string | null; lockUntil?: string | null;
password?: string | null; password?: string | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "LexicalInBlock".
*/
export interface LexicalInBlock {
id: string;
blocks?:
| {
lexical?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
blockName?: string | null;
blockType: 'lexicalInBlock2';
}[]
| null;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "array-fields". * via the `definition` "array-fields".
@@ -1783,6 +1816,10 @@ export interface PayloadLockedDocument {
relationTo: 'users'; relationTo: 'users';
value: string | User; value: string | User;
} | null) } | null)
| ({
relationTo: 'LexicalInBlock';
value: string | LexicalInBlock;
} | null)
| ({ | ({
relationTo: 'array-fields'; relationTo: 'array-fields';
value: string | ArrayField; value: string | ArrayField;
@@ -2025,6 +2062,25 @@ export interface UsersSelect<T extends boolean = true> {
loginAttempts?: T; loginAttempts?: T;
lockUntil?: T; lockUntil?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "LexicalInBlock_select".
*/
export interface LexicalInBlockSelect<T extends boolean = true> {
blocks?:
| T
| {
lexicalInBlock2?:
| T
| {
lexical?: T;
id?: T;
blockName?: T;
};
};
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "array-fields_select". * via the `definition` "array-fields_select".

View File

@@ -367,7 +367,7 @@ export const seed = async (_payload: Payload) => {
collection: lexicalLocalizedFieldsSlug, collection: lexicalLocalizedFieldsSlug,
data: { data: {
title: 'Localized Lexical en', title: 'Localized Lexical en',
lexicalBlocksLocalized: textToLexicalJSON({ text: 'English text' }) as any, lexicalBlocksLocalized: textToLexicalJSON({ text: 'English text' }),
lexicalBlocksSubLocalized: generateLexicalLocalizedRichText( lexicalBlocksSubLocalized: generateLexicalLocalizedRichText(
'Shared text', 'Shared text',
'English text in block', 'English text in block',
@@ -381,7 +381,7 @@ export const seed = async (_payload: Payload) => {
await _payload.create({ await _payload.create({
collection: lexicalRelationshipFieldsSlug, collection: lexicalRelationshipFieldsSlug,
data: { data: {
richText: textToLexicalJSON({ text: 'English text' }) as any, richText: textToLexicalJSON({ text: 'English text' }),
}, },
depth: 0, depth: 0,
overrideAccess: true, overrideAccess: true,
@@ -392,7 +392,7 @@ export const seed = async (_payload: Payload) => {
id: lexicalLocalizedDoc1.id, id: lexicalLocalizedDoc1.id,
data: { data: {
title: 'Localized Lexical es', title: 'Localized Lexical es',
lexicalBlocksLocalized: textToLexicalJSON({ text: 'Spanish text' }) as any, lexicalBlocksLocalized: textToLexicalJSON({ text: 'Spanish text' }),
lexicalBlocksSubLocalized: generateLexicalLocalizedRichText( lexicalBlocksSubLocalized: generateLexicalLocalizedRichText(
'Shared text', 'Shared text',
'Spanish text in block', 'Spanish text in block',
@@ -408,10 +408,7 @@ export const seed = async (_payload: Payload) => {
collection: lexicalLocalizedFieldsSlug, collection: lexicalLocalizedFieldsSlug,
data: { data: {
title: 'Localized Lexical en 2', title: 'Localized Lexical en 2',
lexicalSimple: textToLexicalJSON({
text: 'English text 2',
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
}),
lexicalBlocksLocalized: textToLexicalJSON({ lexicalBlocksLocalized: textToLexicalJSON({
text: 'English text 2', text: 'English text 2',
lexicalLocalizedRelID: lexicalLocalizedDoc1.id, lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
@@ -431,10 +428,7 @@ export const seed = async (_payload: Payload) => {
id: lexicalLocalizedDoc2.id, id: lexicalLocalizedDoc2.id,
data: { data: {
title: 'Localized Lexical es 2', title: 'Localized Lexical es 2',
lexicalSimple: textToLexicalJSON({
text: 'Spanish text 2',
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
}),
lexicalBlocksLocalized: textToLexicalJSON({ lexicalBlocksLocalized: textToLexicalJSON({
text: 'Spanish text 2', text: 'Spanish text 2',
lexicalLocalizedRelID: lexicalLocalizedDoc1.id, lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
@@ -479,6 +473,24 @@ export const seed = async (_payload: Payload) => {
text: 'text', text: 'text',
}, },
}) })
await _payload.create({
collection: 'LexicalInBlock',
data: {
blocks: [
{
blockType: 'lexicalInBlock2',
blockName: '1',
lexical: textToLexicalJSON({ text: '1' }),
},
{
blockType: 'lexicalInBlock2',
blockName: '2',
lexical: textToLexicalJSON({ text: '2' }),
},
],
},
})
} }
export async function clearAndSeedEverything(_payload: Payload) { export async function clearAndSeedEverything(_payload: Payload) {