document data in groups not accessible in conditional statements in other groups

default discord avatar
ToneseekerMusicallast year
1 2

Bug Report

When using conditional statements on fields within a group, data from other groups returns undefined causing the application to crash on page load.

an example:

import { CollectionConfig } from "payload/types";

export const TestConfig: CollectionConfig = {
    slug: 'testconfig',
    fields: [
      {
        name: 'testGroup1',
        type: 'group',
        fields: [
          {
            name: 'testField1',
            type: 'text',
          }
        ]
      },
      {
        name: 'testGroup2',
        type: 'group',
        fields: [
          {
            name: 'testField2',
            type: 'text',
            admin:{
              condition: (data) => {
                if (
                  data.testGroup1.testField1
                ) {
                  return true;
                } else {
                  return false;
                }
              },
            }
          }
        ]
      },
    ]
  }

The conditional statement on the testField2 field does not evaluate correctly when creating a new document. The conditional statement can access the data when editing existing documents where testField1 is already populated, while it crashes the page when creating a new document

Steps to Reproduce

  1. Create a collection with two top level groups.
  2. Define a field in the first group.
  3. Define a field in the second group with a conditional statement dependent on the field in the first group.
  4. Create a new document in the collection.

Other Details

I'm currently running Payload 1.5.8

New document error stack trace:

TestConfig.ts:48 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'testField1')
    at Object.condition (TestConfig.ts:48:51)
    at iterateFields.js:12:1
    at Array.forEach (<anonymous>)
    at iterateFields (iterateFields.js:8:1)
    at addFieldStatePromise (addFieldStatePromise.js:142:1)
    at async Promise.all (index 0)
    at async iterateFields (iterateFields.js:28:1)
    at async Promise.all (index 1)
    at async addFieldStatePromise (addFieldStatePromise.js:196:1)
    at async Promise.all (index 0)
  • Selected Answer
    discord user avatar
    jmikrut
    last year

    Hey @ToneseekerMusical — this is actually intended behavior, because when you create a document, there is no data yet. You should write your conditions so that you access those nested properties safely, in case there is no data. For example:

              {
                name: 'testField2',
                type: 'text',
                admin:{
                  condition: (data) => {
                    if (
                      data.testGroup1?.testField1 // added ?. instead of . 
                    ) {
                      return true;
                    } else {
                      return false;
                    }
                  },
                }
              }
    

    Does that make sense? I will convert this to a discussion so we can keep conversation going.

    Overall though it's a good practice to safely access nested data in conditions, because of the exact thing you're running into. The data structure might not be there yet!

    1 reply
  • default discord avatar
    ToneseekerMusicallast year

    It makes complete sense, I'm just dumb! I'll be sure to keep that in mind moving forward. This is the first time I've ever ran into this, and because I've never had my conditional statements return undefined despite never accessing them safely, I thought it was a bug. I appreciate the help!

  • default discord avatar
    ToneseekerMusicallast year

    Additionally, the following code crashes the application on editing existing documents, even if it has the roles field set:

    export const Users: CollectionConfig = {
      slug: 'users',
      auth: true,
      admin: {
        useAsTitle: 'email',
        disableDuplicate: true,
      },
      access: {
        create: () => true,
        read: isAdminOrPartnerAdmin,
        update: isAdminPartnerAdminOrSelf,
        delete: isAdminPartnerAdminOrSelf,
      },
      fields: [
        {
          type: 'tabs',
          tabs: [
            {
              label: 'User Info',
              fields: [
                {
                  type: 'group',
                  name: 'userInfo',
                  label: ' ',
                  fields: [
                    {
                      type: 'row',
                      fields: [
                        {
                          name: 'roles',
                          type: 'select',
                          required: true,
                          hasMany: true,
                          access: {
                            read: () => true,
                            create: isAdminOrPartnerAdminFieldLevel,
                            update: isAdminOrPartnerAdminFieldLevel,
                          },
                          defaultValue: 'customer',
                          options: [
                            {
                              label: 'Admin',
                              value: 'admin',
                            },
                            {
                              label: 'Employee',
                              value: 'employee',
                            },
                            {
                              label: 'Customer',
                              value: 'customer',
                            },
                          ],
                        },
                      ],
                    },
                  ],
                },
              ]
            },
            {
              label: 'Instruments',
              fields: [
                {
                  name: 'instrumentInfo',
                  type: 'group',
                  label: ' ',
                  admin: {
                    condition: (data, siblingData) => {
                      if (
                        data.userInfo.roles &&
                        (data.userInfo.roles.includes('employee') ||
                          data.userInfo.roles.includes('customer'))
                      ) {
                        return true;
                      } else {
                        return false;
                      }
                    },
                  },
                  fields: [
                    {
                      name: 'instruments',
                      type: 'relationship',
                      relationTo: 'instrument',
                      hasMany: true,
                      required: false,
                      access: {
                        read: () => true,
                        create: isAdminFieldLevel,
                        update: isAdminFieldLevel,
                      },
                    },
                    {
                      name: 'registerInstrument',
                      type: 'text',
                      admin: {
                        condition: (data, siblingData) => {
                          console.log(data.userInfo.roles)
                          if (data.userInfo.roles  &&
                          data.userInfo.roles.includes('customer') {
                            return true;
                          } else {
                            return false;
                          }
                        },
                      },
                    },
                  ],
                }
              ],
            },
          ],
        },
      ],
    };
    

    the field that is causing the crash is registerInstrument, when the conditional statement is removed, you can edit existing and create new users, and when the conditional statement is present, you cannot edit existing or create new users. The console.log however, returns the value of the roles field when attempting to edit existing users.

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.