diff --git a/packages/payload/src/fields/hooks/beforeValidate/promise.ts b/packages/payload/src/fields/hooks/beforeValidate/promise.ts index 59ba4a8a7..e1ab380ea 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/promise.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/promise.ts @@ -274,6 +274,23 @@ export const promise = async ({ } } + if (typeof siblingData[field.name] === 'undefined') { + // If no incoming data, but existing document data is found, merge it in + if (typeof siblingDoc[field.name] !== 'undefined') { + siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name]) + + // Otherwise compute default value + } else if (typeof field.defaultValue !== 'undefined') { + siblingData[field.name] = await getDefaultValue({ + defaultValue: field.defaultValue, + locale: req.locale, + req, + user: req.user, + value: siblingData[field.name], + }) + } + } + // Execute hooks if (field.hooks?.beforeValidate) { for (const hook of field.hooks.beforeValidate) { @@ -314,23 +331,6 @@ export const promise = async ({ delete siblingData[field.name] } } - - if (typeof siblingData[field.name] === 'undefined') { - // If no incoming data, but existing document data is found, merge it in - if (typeof siblingDoc[field.name] !== 'undefined') { - siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name]) - - // Otherwise compute default value - } else if (typeof field.defaultValue !== 'undefined') { - siblingData[field.name] = await getDefaultValue({ - defaultValue: field.defaultValue, - locale: req.locale, - req, - user: req.user, - value: siblingData[field.name], - }) - } - } } // Traverse subfields diff --git a/test/hooks/collections/Value/index.ts b/test/hooks/collections/Value/index.ts new file mode 100644 index 000000000..b1b07f63f --- /dev/null +++ b/test/hooks/collections/Value/index.ts @@ -0,0 +1,34 @@ +import type { CollectionConfig } from 'payload' + +export const valueHooksSlug = 'value-hooks' +export const ValueCollection: CollectionConfig = { + slug: valueHooksSlug, + fields: [ + { + name: 'slug', + type: 'text', + hooks: { + beforeValidate: [ + ({ value, siblingData }) => { + siblingData.beforeValidate_value = String(value) + return value + }, + ], + beforeChange: [ + ({ value, siblingData }) => { + siblingData.beforeChange_value = String(value) + return value + }, + ], + }, + }, + { + name: 'beforeValidate_value', + type: 'text', + }, + { + name: 'beforeChange_value', + type: 'text', + }, + ], +} diff --git a/test/hooks/config.ts b/test/hooks/config.ts index a3247abca..703b5319a 100644 --- a/test/hooks/config.ts +++ b/test/hooks/config.ts @@ -19,6 +19,7 @@ import NestedAfterReadHooks from './collections/NestedAfterReadHooks/index.js' import Relations from './collections/Relations/index.js' import TransformHooks from './collections/Transform/index.js' import Users, { seedHooksUsers } from './collections/Users/index.js' +import { ValueCollection } from './collections/Value/index.js' import { DataHooksGlobal } from './globals/Data/index.js' export const HooksConfig: Promise = buildConfigWithDefaults({ @@ -40,6 +41,7 @@ export const HooksConfig: Promise = buildConfigWithDefaults({ Users, DataHooks, FieldPaths, + ValueCollection, ], globals: [DataHooksGlobal], endpoints: [ diff --git a/test/hooks/int.spec.ts b/test/hooks/int.spec.ts index 8d2160987..ef3786fe5 100644 --- a/test/hooks/int.spec.ts +++ b/test/hooks/int.spec.ts @@ -21,9 +21,10 @@ import { import { relationsSlug } from './collections/Relations/index.js' import { transformSlug } from './collections/Transform/index.js' import { hooksUsersSlug } from './collections/Users/index.js' -import { beforeValidateSlug, fieldPathsSlug } from './shared.js' +import { valueHooksSlug } from './collections/Value/index.js' import { HooksConfig } from './config.js' import { dataHooksGlobalSlug } from './globals/Data/index.js' +import { beforeValidateSlug, fieldPathsSlug } from './shared.js' let restClient: NextRESTClient let payload: Payload @@ -612,6 +613,24 @@ describe('Hooks', () => { }), }) }) + + it('should assign value properly when missing in data', async () => { + const doc = await payload.create({ + collection: valueHooksSlug, + data: { + slug: 'test', + }, + }) + + const updatedDoc = await payload.update({ + id: doc.id, + collection: valueHooksSlug, + data: {}, + }) + + expect(updatedDoc.beforeValidate_value).toEqual('test') + expect(updatedDoc.beforeChange_value).toEqual('test') + }) }) describe('config level after error hook', () => { diff --git a/test/hooks/payload-types.ts b/test/hooks/payload-types.ts index 450f18908..87ad25904 100644 --- a/test/hooks/payload-types.ts +++ b/test/hooks/payload-types.ts @@ -78,6 +78,7 @@ export interface Config { 'hooks-users': HooksUser; 'data-hooks': DataHook; 'field-paths': FieldPath; + 'value-hooks': ValueHook; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -96,6 +97,7 @@ export interface Config { 'hooks-users': HooksUsersSelect | HooksUsersSelect; 'data-hooks': DataHooksSelect | DataHooksSelect; 'field-paths': FieldPathsSelect | FieldPathsSelect; + 'value-hooks': ValueHooksSelect | ValueHooksSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; @@ -610,6 +612,18 @@ export interface FieldPath { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "value-hooks". + */ +export interface ValueHook { + id: string; + slug?: string | null; + beforeValidate_value?: string | null; + beforeChange_value?: string | null; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -664,6 +678,10 @@ export interface PayloadLockedDocument { | ({ relationTo: 'field-paths'; value: string | FieldPath; + } | null) + | ({ + relationTo: 'value-hooks'; + value: string | ValueHook; } | null); globalSlug?: string | null; user: { @@ -910,6 +928,17 @@ export interface FieldPathsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "value-hooks_select". + */ +export interface ValueHooksSelect { + slug?: T; + beforeValidate_value?: T; + beforeChange_value?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select".