diff --git a/packages/payload/src/fields/hooks/afterChange/promise.ts b/packages/payload/src/fields/hooks/afterChange/promise.ts index dd67284b87a..e38d17dc57e 100644 --- a/packages/payload/src/fields/hooks/afterChange/promise.ts +++ b/packages/payload/src/fields/hooks/afterChange/promise.ts @@ -72,6 +72,10 @@ export const promise = async ({ const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : [] const getNestedValue = (data: JsonObject, path: string[]) => path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), data) + const previousValData = + previousSiblingDoc && Object.keys(previousSiblingDoc).length > 0 + ? previousSiblingDoc + : previousDoc if (fieldAffectsData(field)) { // Execute hooks @@ -90,7 +94,8 @@ export const promise = async ({ path: pathSegments, previousDoc, previousSiblingDoc, - previousValue: getNestedValue(previousDoc, pathSegments) ?? previousDoc?.[field.name], + previousValue: + getNestedValue(previousValData, pathSegments) ?? previousValData?.[field.name], req, schemaPath: schemaPathSegments, siblingData, @@ -172,7 +177,7 @@ export const promise = async ({ parentPath: path + '.' + rowIndex, parentSchemaPath: schemaPath + '.' + block.slug, previousDoc, - previousSiblingDoc: previousDoc?.[field.name]?.[rowIndex] || ({} as JsonObject), + previousSiblingDoc: previousValData?.[field.name]?.[rowIndex] || ({} as JsonObject), req, siblingData: siblingData?.[field.name]?.[rowIndex] || {}, siblingDoc: row ? { ...row } : {}, diff --git a/test/hooks/collections/NestedAfterChangeHook/index.ts b/test/hooks/collections/NestedAfterChangeHook/index.ts index b61e8f7b11b..b06d27e9867 100644 --- a/test/hooks/collections/NestedAfterChangeHook/index.ts +++ b/test/hooks/collections/NestedAfterChangeHook/index.ts @@ -1,4 +1,6 @@ import type { CollectionConfig } from 'payload' + +import { BlocksFeature, lexicalEditor, LinkFeature } from '@payloadcms/richtext-lexical' export const nestedAfterChangeHooksSlug = 'nested-after-change-hooks' const NestedAfterChangeHooks: CollectionConfig = { @@ -22,7 +24,6 @@ const NestedAfterChangeHooks: CollectionConfig = { hooks: { afterChange: [ ({ previousValue, operation }) => { - console.log(previousValue) if (operation === 'update' && typeof previousValue === 'undefined') { throw new Error('previousValue is missing in nested beforeChange hook') } @@ -34,6 +35,68 @@ const NestedAfterChangeHooks: CollectionConfig = { }, ], }, + { + name: 'lexical', + type: 'richText', + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + BlocksFeature({ + blocks: [ + { + slug: 'nestedBlock', + fields: [ + { + type: 'text', + name: 'nestedAfterChange', + hooks: { + afterChange: [ + ({ previousValue, operation }) => { + if (operation === 'update' && typeof previousValue === 'undefined') { + throw new Error('previousValue is missing in nested beforeChange hook') + } + }, + ], + }, + }, + ], + }, + ], + }), + LinkFeature({ + fields: [ + { + type: 'blocks', + name: 'linkBlocks', + blocks: [ + { + slug: 'nestedLinkBlock', + fields: [ + { + name: 'nestedRelationship', + type: 'relationship', + relationTo: 'relations', + hooks: { + afterChange: [ + ({ previousValue, operation }) => { + if (operation === 'update' && typeof previousValue === 'undefined') { + throw new Error( + 'previousValue is missing in nested beforeChange hook', + ) + } + }, + ], + }, + }, + ], + }, + ], + }, + ], + }), + ], + }), + }, ], } diff --git a/test/hooks/int.spec.ts b/test/hooks/int.spec.ts index 43743465d90..122bfc8bbc9 100644 --- a/test/hooks/int.spec.ts +++ b/test/hooks/int.spec.ts @@ -368,6 +368,111 @@ describe('Hooks', () => { expect(updatedDoc).toBeDefined() }) + + it('should populate previousValue in Lexical nested afterChange hooks', async () => { + const relationID = await payload.create({ + collection: 'relations', + data: { + title: 'Relation for nested afterChange', + }, + }) + + // this collection will throw an error if previousValue is not defined in nested afterChange hook + const nestedAfterChangeDoc = await payload.create({ + collection: nestedAfterChangeHooksSlug, + data: { + text: 'initial', + group: { + array: [ + { + nestedAfterChange: 'initial', + }, + ], + }, + lexical: { + root: { + children: [ + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'link', + type: 'text', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'link', + version: 3, + fields: { + linkBlocks: [ + { + id: '693ade72068ea07ba13edcab', + blockType: 'nestedLinkBlock', + nestedRelationship: relationID.id, + }, + ], + }, + id: '693ade70068ea07ba13edca9', + }, + ], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + textFormat: 0, + textStyle: '', + }, + { + type: 'block', + version: 2, + format: '', + fields: { + id: '693adf3c068ea07ba13edcae', + blockName: '', + nestedAfterChange: 'test', + blockType: 'nestedBlock', + }, + }, + { + children: [], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + textFormat: 0, + textStyle: '', + }, + ], + direction: null, + format: '', + indent: 0, + type: 'root', + version: 1, + }, + }, + }, + }) + + await expect( + payload.update({ + collection: 'nested-after-change-hooks', + id: nestedAfterChangeDoc.id, + data: { + text: 'updated', + }, + }), + ).resolves.not.toThrow() + }) }) describe('auth collection hooks', () => { diff --git a/test/hooks/payload-types.ts b/test/hooks/payload-types.ts index ffe8b949d14..92f2eb15d7d 100644 --- a/test/hooks/payload-types.ts +++ b/test/hooks/payload-types.ts @@ -288,6 +288,21 @@ export interface NestedAfterChangeHook { }[] | null; }; + lexical?: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; updatedAt: string; createdAt: string; } @@ -943,6 +958,7 @@ export interface NestedAfterChangeHooksSelect { id?: T; }; }; + lexical?: T; updatedAt?: T; createdAt?: T; }