fix: incorrect value inside beforeValidate field hooks (#11433)
### What? `value` within the beforeValidate field hook was not correctly falling back to the document value when no value was passed inside the request for the field. ### Why? The fallback logic was running after the beforeValidate field hooks are called. ### How? Run the fallback logic before running the beforeValidate field hooks. Fixes https://github.com/payloadcms/payload/issues/10923
This commit is contained in:
@@ -274,6 +274,23 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Execute hooks
|
||||||
if (field.hooks?.beforeValidate) {
|
if (field.hooks?.beforeValidate) {
|
||||||
for (const hook of field.hooks.beforeValidate) {
|
for (const hook of field.hooks.beforeValidate) {
|
||||||
@@ -314,23 +331,6 @@ export const promise = async <T>({
|
|||||||
delete siblingData[field.name]
|
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
|
// Traverse subfields
|
||||||
|
|||||||
34
test/hooks/collections/Value/index.ts
Normal file
34
test/hooks/collections/Value/index.ts
Normal file
@@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import NestedAfterReadHooks from './collections/NestedAfterReadHooks/index.js'
|
|||||||
import Relations from './collections/Relations/index.js'
|
import Relations from './collections/Relations/index.js'
|
||||||
import TransformHooks from './collections/Transform/index.js'
|
import TransformHooks from './collections/Transform/index.js'
|
||||||
import Users, { seedHooksUsers } from './collections/Users/index.js'
|
import Users, { seedHooksUsers } from './collections/Users/index.js'
|
||||||
|
import { ValueCollection } from './collections/Value/index.js'
|
||||||
import { DataHooksGlobal } from './globals/Data/index.js'
|
import { DataHooksGlobal } from './globals/Data/index.js'
|
||||||
|
|
||||||
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||||
@@ -40,6 +41,7 @@ export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
|||||||
Users,
|
Users,
|
||||||
DataHooks,
|
DataHooks,
|
||||||
FieldPaths,
|
FieldPaths,
|
||||||
|
ValueCollection,
|
||||||
],
|
],
|
||||||
globals: [DataHooksGlobal],
|
globals: [DataHooksGlobal],
|
||||||
endpoints: [
|
endpoints: [
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ import {
|
|||||||
import { relationsSlug } from './collections/Relations/index.js'
|
import { relationsSlug } from './collections/Relations/index.js'
|
||||||
import { transformSlug } from './collections/Transform/index.js'
|
import { transformSlug } from './collections/Transform/index.js'
|
||||||
import { hooksUsersSlug } from './collections/Users/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 { HooksConfig } from './config.js'
|
||||||
import { dataHooksGlobalSlug } from './globals/Data/index.js'
|
import { dataHooksGlobalSlug } from './globals/Data/index.js'
|
||||||
|
import { beforeValidateSlug, fieldPathsSlug } from './shared.js'
|
||||||
|
|
||||||
let restClient: NextRESTClient
|
let restClient: NextRESTClient
|
||||||
let payload: Payload
|
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', () => {
|
describe('config level after error hook', () => {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export interface Config {
|
|||||||
'hooks-users': HooksUser;
|
'hooks-users': HooksUser;
|
||||||
'data-hooks': DataHook;
|
'data-hooks': DataHook;
|
||||||
'field-paths': FieldPath;
|
'field-paths': FieldPath;
|
||||||
|
'value-hooks': ValueHook;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
'payload-migrations': PayloadMigration;
|
'payload-migrations': PayloadMigration;
|
||||||
@@ -96,6 +97,7 @@ export interface Config {
|
|||||||
'hooks-users': HooksUsersSelect<false> | HooksUsersSelect<true>;
|
'hooks-users': HooksUsersSelect<false> | HooksUsersSelect<true>;
|
||||||
'data-hooks': DataHooksSelect<false> | DataHooksSelect<true>;
|
'data-hooks': DataHooksSelect<false> | DataHooksSelect<true>;
|
||||||
'field-paths': FieldPathsSelect<false> | FieldPathsSelect<true>;
|
'field-paths': FieldPathsSelect<false> | FieldPathsSelect<true>;
|
||||||
|
'value-hooks': ValueHooksSelect<false> | ValueHooksSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
@@ -610,6 +612,18 @@ export interface FieldPath {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-locked-documents".
|
* via the `definition` "payload-locked-documents".
|
||||||
@@ -664,6 +678,10 @@ export interface PayloadLockedDocument {
|
|||||||
| ({
|
| ({
|
||||||
relationTo: 'field-paths';
|
relationTo: 'field-paths';
|
||||||
value: string | FieldPath;
|
value: string | FieldPath;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'value-hooks';
|
||||||
|
value: string | ValueHook;
|
||||||
} | null);
|
} | null);
|
||||||
globalSlug?: string | null;
|
globalSlug?: string | null;
|
||||||
user: {
|
user: {
|
||||||
@@ -910,6 +928,17 @@ export interface FieldPathsSelect<T extends boolean = true> {
|
|||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "value-hooks_select".
|
||||||
|
*/
|
||||||
|
export interface ValueHooksSelect<T extends boolean = true> {
|
||||||
|
slug?: T;
|
||||||
|
beforeValidate_value?: T;
|
||||||
|
beforeChange_value?: 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` "payload-locked-documents_select".
|
* via the `definition` "payload-locked-documents_select".
|
||||||
|
|||||||
Reference in New Issue
Block a user