Skip to content

Commit b3e7a9d

Browse files
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 #10923
1 parent c4bc0ae commit b3e7a9d

File tree

5 files changed

+102
-18
lines changed

5 files changed

+102
-18
lines changed

packages/payload/src/fields/hooks/beforeValidate/promise.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ export const promise = async <T>({
274274
}
275275
}
276276

277+
if (typeof siblingData[field.name] === 'undefined') {
278+
// If no incoming data, but existing document data is found, merge it in
279+
if (typeof siblingDoc[field.name] !== 'undefined') {
280+
siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name])
281+
282+
// Otherwise compute default value
283+
} else if (typeof field.defaultValue !== 'undefined') {
284+
siblingData[field.name] = await getDefaultValue({
285+
defaultValue: field.defaultValue,
286+
locale: req.locale,
287+
req,
288+
user: req.user,
289+
value: siblingData[field.name],
290+
})
291+
}
292+
}
293+
277294
// Execute hooks
278295
if (field.hooks?.beforeValidate) {
279296
for (const hook of field.hooks.beforeValidate) {
@@ -314,23 +331,6 @@ export const promise = async <T>({
314331
delete siblingData[field.name]
315332
}
316333
}
317-
318-
if (typeof siblingData[field.name] === 'undefined') {
319-
// If no incoming data, but existing document data is found, merge it in
320-
if (typeof siblingDoc[field.name] !== 'undefined') {
321-
siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name])
322-
323-
// Otherwise compute default value
324-
} else if (typeof field.defaultValue !== 'undefined') {
325-
siblingData[field.name] = await getDefaultValue({
326-
defaultValue: field.defaultValue,
327-
locale: req.locale,
328-
req,
329-
user: req.user,
330-
value: siblingData[field.name],
331-
})
332-
}
333-
}
334334
}
335335

336336
// Traverse subfields
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
export const valueHooksSlug = 'value-hooks'
4+
export const ValueCollection: CollectionConfig = {
5+
slug: valueHooksSlug,
6+
fields: [
7+
{
8+
name: 'slug',
9+
type: 'text',
10+
hooks: {
11+
beforeValidate: [
12+
({ value, siblingData }) => {
13+
siblingData.beforeValidate_value = String(value)
14+
return value
15+
},
16+
],
17+
beforeChange: [
18+
({ value, siblingData }) => {
19+
siblingData.beforeChange_value = String(value)
20+
return value
21+
},
22+
],
23+
},
24+
},
25+
{
26+
name: 'beforeValidate_value',
27+
type: 'text',
28+
},
29+
{
30+
name: 'beforeChange_value',
31+
type: 'text',
32+
},
33+
],
34+
}

test/hooks/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import NestedAfterReadHooks from './collections/NestedAfterReadHooks/index.js'
1919
import Relations from './collections/Relations/index.js'
2020
import TransformHooks from './collections/Transform/index.js'
2121
import Users, { seedHooksUsers } from './collections/Users/index.js'
22+
import { ValueCollection } from './collections/Value/index.js'
2223
import { DataHooksGlobal } from './globals/Data/index.js'
2324

2425
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
@@ -40,6 +41,7 @@ export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
4041
Users,
4142
DataHooks,
4243
FieldPaths,
44+
ValueCollection,
4345
],
4446
globals: [DataHooksGlobal],
4547
endpoints: [

test/hooks/int.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import {
2121
import { relationsSlug } from './collections/Relations/index.js'
2222
import { transformSlug } from './collections/Transform/index.js'
2323
import { hooksUsersSlug } from './collections/Users/index.js'
24-
import { beforeValidateSlug, fieldPathsSlug } from './shared.js'
24+
import { valueHooksSlug } from './collections/Value/index.js'
2525
import { HooksConfig } from './config.js'
2626
import { dataHooksGlobalSlug } from './globals/Data/index.js'
27+
import { beforeValidateSlug, fieldPathsSlug } from './shared.js'
2728

2829
let restClient: NextRESTClient
2930
let payload: Payload
@@ -612,6 +613,24 @@ describe('Hooks', () => {
612613
}),
613614
})
614615
})
616+
617+
it('should assign value properly when missing in data', async () => {
618+
const doc = await payload.create({
619+
collection: valueHooksSlug,
620+
data: {
621+
slug: 'test',
622+
},
623+
})
624+
625+
const updatedDoc = await payload.update({
626+
id: doc.id,
627+
collection: valueHooksSlug,
628+
data: {},
629+
})
630+
631+
expect(updatedDoc.beforeValidate_value).toEqual('test')
632+
expect(updatedDoc.beforeChange_value).toEqual('test')
633+
})
615634
})
616635

617636
describe('config level after error hook', () => {

test/hooks/payload-types.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export interface Config {
7878
'hooks-users': HooksUser;
7979
'data-hooks': DataHook;
8080
'field-paths': FieldPath;
81+
'value-hooks': ValueHook;
8182
'payload-locked-documents': PayloadLockedDocument;
8283
'payload-preferences': PayloadPreference;
8384
'payload-migrations': PayloadMigration;
@@ -96,6 +97,7 @@ export interface Config {
9697
'hooks-users': HooksUsersSelect<false> | HooksUsersSelect<true>;
9798
'data-hooks': DataHooksSelect<false> | DataHooksSelect<true>;
9899
'field-paths': FieldPathsSelect<false> | FieldPathsSelect<true>;
100+
'value-hooks': ValueHooksSelect<false> | ValueHooksSelect<true>;
99101
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
100102
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
101103
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
@@ -610,6 +612,18 @@ export interface FieldPath {
610612
updatedAt: string;
611613
createdAt: string;
612614
}
615+
/**
616+
* This interface was referenced by `Config`'s JSON-Schema
617+
* via the `definition` "value-hooks".
618+
*/
619+
export interface ValueHook {
620+
id: string;
621+
slug?: string | null;
622+
beforeValidate_value?: string | null;
623+
beforeChange_value?: string | null;
624+
updatedAt: string;
625+
createdAt: string;
626+
}
613627
/**
614628
* This interface was referenced by `Config`'s JSON-Schema
615629
* via the `definition` "payload-locked-documents".
@@ -664,6 +678,10 @@ export interface PayloadLockedDocument {
664678
| ({
665679
relationTo: 'field-paths';
666680
value: string | FieldPath;
681+
} | null)
682+
| ({
683+
relationTo: 'value-hooks';
684+
value: string | ValueHook;
667685
} | null);
668686
globalSlug?: string | null;
669687
user: {
@@ -910,6 +928,17 @@ export interface FieldPathsSelect<T extends boolean = true> {
910928
updatedAt?: T;
911929
createdAt?: T;
912930
}
931+
/**
932+
* This interface was referenced by `Config`'s JSON-Schema
933+
* via the `definition` "value-hooks_select".
934+
*/
935+
export interface ValueHooksSelect<T extends boolean = true> {
936+
slug?: T;
937+
beforeValidate_value?: T;
938+
beforeChange_value?: T;
939+
updatedAt?: T;
940+
createdAt?: T;
941+
}
913942
/**
914943
* This interface was referenced by `Config`'s JSON-Schema
915944
* via the `definition` "payload-locked-documents_select".

0 commit comments

Comments
 (0)