fix: handle undefined values in afterChange hooks when read:false and create:true on the field level access for parents and siblings (#12664)
<!-- Thank you for the PR! Please go through the checklist below and make sure you've completed all the steps. Please review the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository if you haven't already. The following items will ensure that your PR is handled as smoothly as possible: - PR Title must follow conventional commits format. For example, `feat: my new feature`, `fix(plugin-seo): my fix`. - Minimal description explained as if explained to someone not immediately familiar with the code. - Provide before/after screenshots or code diffs if applicable. - Link any related issues/discussions from GitHub or Discord. - Add review comments if necessary to explain to the reviewer the logic behind a change --> ### What? Fixes a bug where `afterChange` hooks would attempt to access values for fields that are `read: false` but `create: true`, resulting in `undefined` values and unexpected behavior. ### Why? In scenarios where access control allows field creation (`create: true`) but disallows reading it (`read: false`), hooks like `afterChange` would still attempt to operate on `undefined` values from `siblingDoc` or `previousDoc`, potentially causing errors or skipped logic. ### How? Adds safe optional chaining and fallback object initialization in `promise.ts` for: - `previousDoc[field.name]` - `siblingDoc[field.name]` - Group, Array, and Block field traversals This ensures that these values are treated as empty objects or arrays where appropriate to prevent runtime errors during traversal or hook execution. Fixes https://github.com/payloadcms/payload/issues/12660 --------- Co-authored-by: Niall Bambury <niall.bambury@cuckoo.co>
This commit is contained in:
@@ -88,12 +88,12 @@ export const promise = async ({
|
||||
path: pathSegments,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name!],
|
||||
previousValue: previousDoc?.[field.name!],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingFields: siblingFields!,
|
||||
value: siblingDoc[field.name!],
|
||||
value: siblingDoc?.[field.name!],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
@@ -226,10 +226,10 @@ export const promise = async ({
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as JsonObject,
|
||||
previousSiblingDoc: (previousDoc?.[field.name] as JsonObject) || {},
|
||||
req,
|
||||
siblingData: (siblingData?.[field.name] as JsonObject) || {},
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
siblingDoc: (siblingDoc?.[field.name] as JsonObject) || {},
|
||||
})
|
||||
} else {
|
||||
await traverseFields({
|
||||
@@ -282,11 +282,11 @@ export const promise = async ({
|
||||
path: pathSegments,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name],
|
||||
previousValue: previousDoc?.[field.name],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
value: siblingDoc[field.name],
|
||||
value: siblingDoc?.[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
@@ -305,9 +305,9 @@ export const promise = async ({
|
||||
const isNamedTab = tabHasName(field)
|
||||
|
||||
if (isNamedTab) {
|
||||
tabSiblingData = (siblingData[field.name] as JsonObject) ?? {}
|
||||
tabSiblingDoc = (siblingDoc[field.name] as JsonObject) ?? {}
|
||||
tabPreviousSiblingDoc = (previousDoc[field.name] as JsonObject) ?? {}
|
||||
tabSiblingData = (siblingData?.[field.name] ?? {}) as JsonObject
|
||||
tabSiblingDoc = (siblingDoc?.[field.name] ?? {}) as JsonObject
|
||||
tabPreviousSiblingDoc = (previousDoc?.[field.name] ?? {}) as JsonObject
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
|
||||
Reference in New Issue
Block a user