fix: presentational-field types incorrectly exposing hooks (#11514)
### What? Presentational Fields such as [Row](https://payloadcms.com/docs/fields/row) are described as only effecting the admin panel. If they do not impact data, their types should not include hooks in the fields config. ### Why? Developers can currently assign hooks to these fields, expecting them to work, when in reality they are not called. ### How? Omit `hooks` from `FieldBase` Fixes #11507 --------- Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
This commit is contained in:
@@ -95,7 +95,8 @@ Here are the available Presentational Fields:
|
|||||||
|
|
||||||
- [Collapsible](../fields/collapsible) - nests fields within a collapsible component
|
- [Collapsible](../fields/collapsible) - nests fields within a collapsible component
|
||||||
- [Row](../fields/row) - aligns fields horizontally
|
- [Row](../fields/row) - aligns fields horizontally
|
||||||
- [Tabs (Unnamed)](../fields/tabs) - nests fields within a tabbed layout
|
- [Tabs (Unnamed)](../fields/tabs) - nests fields within a tabbed layout. It is not presentational if the tab has a name.
|
||||||
|
- [Group (Unnamed)](../fields/group) - nests fields within a keyed object It is not presentational if the group has a name.
|
||||||
- [UI](../fields/ui) - blank field for custom UI components
|
- [UI](../fields/ui) - blank field for custom UI components
|
||||||
|
|
||||||
### Virtual Fields
|
### Virtual Fields
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ export type NamedGroupField = {
|
|||||||
export type UnnamedGroupField = {
|
export type UnnamedGroupField = {
|
||||||
interfaceName?: never
|
interfaceName?: never
|
||||||
localized?: never
|
localized?: never
|
||||||
} & Omit<GroupBase, 'name' | 'virtual'>
|
} & Omit<GroupBase, 'hooks' | 'name' | 'virtual'>
|
||||||
|
|
||||||
export type GroupField = NamedGroupField | UnnamedGroupField
|
export type GroupField = NamedGroupField | UnnamedGroupField
|
||||||
|
|
||||||
@@ -777,7 +777,7 @@ export type RowField = {
|
|||||||
admin?: Omit<Admin, 'description'>
|
admin?: Omit<Admin, 'description'>
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
type: 'row'
|
type: 'row'
|
||||||
} & Omit<FieldBase, 'admin' | 'label' | 'localized' | 'name' | 'validate' | 'virtual'>
|
} & Omit<FieldBase, 'admin' | 'hooks' | 'label' | 'localized' | 'name' | 'validate' | 'virtual'>
|
||||||
|
|
||||||
export type RowFieldClient = {
|
export type RowFieldClient = {
|
||||||
admin?: Omit<AdminClient, 'description'>
|
admin?: Omit<AdminClient, 'description'>
|
||||||
@@ -816,7 +816,7 @@ export type CollapsibleField = {
|
|||||||
label: Required<FieldBase['label']>
|
label: Required<FieldBase['label']>
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
Omit<FieldBase, 'label' | 'localized' | 'name' | 'validate' | 'virtual'>
|
Omit<FieldBase, 'hooks' | 'label' | 'localized' | 'name' | 'validate' | 'virtual'>
|
||||||
|
|
||||||
export type CollapsibleFieldClient = {
|
export type CollapsibleFieldClient = {
|
||||||
admin?: {
|
admin?: {
|
||||||
@@ -863,7 +863,7 @@ export type UnnamedTab = {
|
|||||||
| LabelFunction
|
| LabelFunction
|
||||||
| string
|
| string
|
||||||
localized?: never
|
localized?: never
|
||||||
} & Omit<TabBase, 'name' | 'virtual'>
|
} & Omit<TabBase, 'hooks' | 'name' | 'virtual'>
|
||||||
|
|
||||||
export type Tab = NamedTab | UnnamedTab
|
export type Tab = NamedTab | UnnamedTab
|
||||||
export type TabsField = {
|
export type TabsField = {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const promise = async ({
|
|||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
// Execute hooks
|
// Execute hooks
|
||||||
if (field.hooks?.afterChange) {
|
if ('hooks' in field && field.hooks?.afterChange) {
|
||||||
for (const hook of field.hooks.afterChange) {
|
for (const hook of field.hooks.afterChange) {
|
||||||
const hookedValue = await hook({
|
const hookedValue = await hook({
|
||||||
blockData,
|
blockData,
|
||||||
@@ -88,16 +88,16 @@ export const promise = async ({
|
|||||||
path: pathSegments,
|
path: pathSegments,
|
||||||
previousDoc,
|
previousDoc,
|
||||||
previousSiblingDoc,
|
previousSiblingDoc,
|
||||||
previousValue: previousDoc?.[field.name!],
|
previousValue: previousDoc?.[field.name],
|
||||||
req,
|
req,
|
||||||
schemaPath: schemaPathSegments,
|
schemaPath: schemaPathSegments,
|
||||||
siblingData,
|
siblingData,
|
||||||
siblingFields: siblingFields!,
|
siblingFields: siblingFields!,
|
||||||
value: siblingDoc?.[field.name!],
|
value: siblingDoc?.[field.name],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hookedValue !== undefined) {
|
if (hookedValue !== undefined) {
|
||||||
siblingDoc[field.name!] = hookedValue
|
siblingDoc[field.name] = hookedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,15 +238,15 @@ export const promise = async ({
|
|||||||
|
|
||||||
if (fieldAffectsDataResult) {
|
if (fieldAffectsDataResult) {
|
||||||
// Execute hooks
|
// Execute hooks
|
||||||
if (triggerHooks && field.hooks?.afterRead) {
|
if (triggerHooks && 'hooks' in field && field.hooks?.afterRead) {
|
||||||
for (const hook of field.hooks.afterRead) {
|
for (const hook of field.hooks.afterRead) {
|
||||||
const shouldRunHookOnAllLocales =
|
const shouldRunHookOnAllLocales =
|
||||||
fieldShouldBeLocalized({ field, parentIsLocalized: parentIsLocalized! }) &&
|
fieldShouldBeLocalized({ field, parentIsLocalized: parentIsLocalized! }) &&
|
||||||
(locale === 'all' || !flattenLocales) &&
|
(locale === 'all' || !flattenLocales) &&
|
||||||
typeof siblingDoc[field.name!] === 'object'
|
typeof siblingDoc[field.name] === 'object'
|
||||||
|
|
||||||
if (shouldRunHookOnAllLocales) {
|
if (shouldRunHookOnAllLocales) {
|
||||||
const localesAndValues = Object.entries(siblingDoc[field.name!])
|
const localesAndValues = Object.entries(siblingDoc[field.name])
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
localesAndValues.map(async ([localeKey, value]) => {
|
localesAndValues.map(async ([localeKey, value]) => {
|
||||||
const hookedValue = await hook({
|
const hookedValue = await hook({
|
||||||
@@ -274,7 +274,7 @@ export const promise = async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (hookedValue !== undefined) {
|
if (hookedValue !== undefined) {
|
||||||
siblingDoc[field.name!][localeKey] = hookedValue
|
siblingDoc[field.name][localeKey] = hookedValue
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -300,11 +300,11 @@ export const promise = async ({
|
|||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
siblingData: siblingDoc,
|
siblingData: siblingDoc,
|
||||||
siblingFields: siblingFields!,
|
siblingFields: siblingFields!,
|
||||||
value: siblingDoc[field.name!],
|
value: siblingDoc[field.name],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hookedValue !== undefined) {
|
if (hookedValue !== undefined) {
|
||||||
siblingDoc[field.name!] = hookedValue
|
siblingDoc[field.name] = hookedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export const promise = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute hooks
|
// Execute hooks
|
||||||
if (field.hooks?.beforeChange) {
|
if ('hooks' in field && field.hooks?.beforeChange) {
|
||||||
for (const hook of field.hooks.beforeChange) {
|
for (const hook of field.hooks.beforeChange) {
|
||||||
const hookedValue = await hook({
|
const hookedValue = await hook({
|
||||||
blockData,
|
blockData,
|
||||||
@@ -143,17 +143,17 @@ export const promise = async ({
|
|||||||
originalDoc: doc,
|
originalDoc: doc,
|
||||||
path: pathSegments,
|
path: pathSegments,
|
||||||
previousSiblingDoc: siblingDoc,
|
previousSiblingDoc: siblingDoc,
|
||||||
previousValue: siblingDoc[field.name!],
|
previousValue: siblingDoc[field.name],
|
||||||
req,
|
req,
|
||||||
schemaPath: schemaPathSegments,
|
schemaPath: schemaPathSegments,
|
||||||
siblingData,
|
siblingData,
|
||||||
siblingDocWithLocales,
|
siblingDocWithLocales,
|
||||||
siblingFields: siblingFields!,
|
siblingFields: siblingFields!,
|
||||||
value: siblingData[field.name!],
|
value: siblingData[field.name],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hookedValue !== undefined) {
|
if (hookedValue !== undefined) {
|
||||||
siblingData[field.name!] = hookedValue
|
siblingData[field.name] = hookedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
// Run field beforeDuplicate hooks.
|
// Run field beforeDuplicate hooks.
|
||||||
// These hooks are responsible for resetting the `id` field values of array and block rows. See `baseIDField`.
|
// These hooks are responsible for resetting the `id` field values of array and block rows. See `baseIDField`.
|
||||||
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
if (Array.isArray('hooks' in field && field.hooks?.beforeDuplicate)) {
|
||||||
if (fieldIsLocalized) {
|
if (fieldIsLocalized) {
|
||||||
const localeData: JsonObject = {}
|
const localeData: JsonObject = {}
|
||||||
|
|
||||||
@@ -90,9 +90,11 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hookResult
|
let hookResult
|
||||||
|
if ('hooks' in field && field.hooks?.beforeDuplicate) {
|
||||||
for (const hook of field.hooks.beforeDuplicate) {
|
for (const hook of field.hooks.beforeDuplicate) {
|
||||||
hookResult = await hook(beforeDuplicateArgs)
|
hookResult = await hook(beforeDuplicateArgs)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof hookResult !== 'undefined') {
|
if (typeof hookResult !== 'undefined') {
|
||||||
localeData[locale] = hookResult
|
localeData[locale] = hookResult
|
||||||
@@ -121,9 +123,11 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hookResult
|
let hookResult
|
||||||
|
if ('hooks' in field && field.hooks?.beforeDuplicate) {
|
||||||
for (const hook of field.hooks.beforeDuplicate) {
|
for (const hook of field.hooks.beforeDuplicate) {
|
||||||
hookResult = await hook(beforeDuplicateArgs)
|
hookResult = await hook(beforeDuplicateArgs)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof hookResult !== 'undefined') {
|
if (typeof hookResult !== 'undefined') {
|
||||||
siblingDoc[field.name!] = hookResult
|
siblingDoc[field.name!] = hookResult
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute hooks
|
// Execute hooks
|
||||||
if (field.hooks?.beforeValidate) {
|
if ('hooks' in field && field.hooks?.beforeValidate) {
|
||||||
for (const hook of field.hooks.beforeValidate) {
|
for (const hook of field.hooks.beforeValidate) {
|
||||||
const hookedValue = await hook({
|
const hookedValue = await hook({
|
||||||
blockData,
|
blockData,
|
||||||
@@ -299,19 +299,19 @@ export const promise = async <T>({
|
|||||||
overrideAccess,
|
overrideAccess,
|
||||||
path: pathSegments,
|
path: pathSegments,
|
||||||
previousSiblingDoc: siblingDoc,
|
previousSiblingDoc: siblingDoc,
|
||||||
previousValue: siblingDoc[field.name!],
|
previousValue: siblingDoc[field.name],
|
||||||
req,
|
req,
|
||||||
schemaPath: schemaPathSegments,
|
schemaPath: schemaPathSegments,
|
||||||
siblingData,
|
siblingData,
|
||||||
siblingFields: siblingFields!,
|
siblingFields: siblingFields!,
|
||||||
value:
|
value:
|
||||||
typeof siblingData[field.name!] === 'undefined'
|
typeof siblingData[field.name] === 'undefined'
|
||||||
? fallbackResult.value
|
? fallbackResult.value
|
||||||
: siblingData[field.name!],
|
: siblingData[field.name],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hookedValue !== undefined) {
|
if (hookedValue !== undefined) {
|
||||||
siblingData[field.name!] = hookedValue
|
siblingData[field.name] = hookedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const setDefaultBeforeDuplicate = (
|
|||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
(('required' in field && field.required) || field.unique) &&
|
(('required' in field && field.required) || field.unique) &&
|
||||||
|
'hooks' in field &&
|
||||||
(!field.hooks?.beforeDuplicate ||
|
(!field.hooks?.beforeDuplicate ||
|
||||||
(Array.isArray(field.hooks.beforeDuplicate) && field.hooks.beforeDuplicate.length === 0))
|
(Array.isArray(field.hooks.beforeDuplicate) && field.hooks.beforeDuplicate.length === 0))
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -119,7 +119,10 @@ export const getFields = ({
|
|||||||
generateFileURL,
|
generateFileURL,
|
||||||
size,
|
size,
|
||||||
}),
|
}),
|
||||||
...(existingSizeURLField?.hooks?.afterRead || []),
|
...((typeof existingSizeURLField === 'object' &&
|
||||||
|
'hooks' in existingSizeURLField &&
|
||||||
|
existingSizeURLField?.hooks?.afterRead) ||
|
||||||
|
[]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user