sanity icon indicating copy to clipboard operation
sanity copied to clipboard

Array item validation leads to `Array item type not found: .undefined`

Open roch-numbered opened this issue 1 year ago β€’ 5 comments

Describe the bug

When a field of an object item from an array has a validation rule, the validation panel produces this error:

Array item type not found: .undefined

To Reproduce

Steps to reproduce the behavior:

  1. With the following schema definition
{
	name: 'list',
	type: 'array',
	of: [{
		title: 'Item',
		type: 'object',
		fields: [
			{
				name: 'title',
				type: 'string',
				validation: Rule => Rule.required()
			},
			{
				name: 'text',
				type: 'string',
			},
		]
	}]
}
  1. Add an item in the array
  2. Fill only text
  3. Click on validation tooltip to open the validation panel
image image image

This happens with a basic Rule.required() validation or with a custom function.

Expected behavior

The validation panel should correctly displays the path to the error.

Additional details

Repro project

Full schema
import { KeyedSegment, defineType } from "sanity";
import {arrayToJSONMatchPath, extractWithPath} from '@sanity/mutator'

export default defineType({
	title: 'Page',
	name: 'page',
	type: 'document',
	fields: [
		{
			name: 'title',
			type: 'string',
		},
		{
			name: 'list',
			type: 'array',
			of: [{
				title: 'Item',
				type: 'object',
				fields: [
					{
						name: 'title',
						type: 'string',
						validation: Rule => Rule.required().custom((currentTitle, {document, path}) => {
							if (typeof currentTitle === 'undefined' || !path) return true

							const grandParentPath = arrayToJSONMatchPath(path.slice(0, -2))
							const list = extractWithPath(grandParentPath, document)?.[0]?.value as {_key: string, title?: string}[] | undefined
							const currentKey = (path.at(-2) as KeyedSegment)?._key

							if (!list) return true

							const hasDuplicates = list
								// filter out item that stores the `currentTitle` value
								.filter(item => item._key !== currentKey)
								// at least one item has the same title that the current one
								.some(item => item.title === currentTitle)

							if (!hasDuplicates) return true

							return `Title '${currentTitle}' is duplicated.`
						})
					}
				]
			}]
		},
		{
			name: 'list2',
			type: 'array',
			of: [{
				title: 'Item',
				type: 'object',
				fields: [
					{
						name: 'title',
						type: 'string',
						validation: Rule => Rule.required()
					},
					{
						name: 'text',
						type: 'string',
					},
				]
			}]
		}
	]
})

Which versions of Sanity are you using?

@sanity/cli (global)          3.23.1 (up to date)
@sanity/eslint-config-studio   3.0.1 (up to date)
@sanity/mutator               3.23.1 (up to date)
@sanity/vision                3.23.1 (up to date)
sanity                        3.23.1 (up to date)

What operating system are you using?

MacOS Ventura 13.5.1

Which versions of Node.js / npm are you running?

$ node -v
v20.10.0
$ pnpm -v
8.12.0

roch-numbered avatar Dec 20 '23 11:12 roch-numbered

Same problem happening to me.

dnzg avatar Jan 10 '24 13:01 dnzg

Same for me, any viable workarounds?

darrenjacoby avatar Jan 18 '24 12:01 darrenjacoby

Hello everyone! Have you tried adding a name to the object? In the code example, I can see there is only a type and a title defined.

bobinska-dev avatar Jan 23 '24 08:01 bobinska-dev

I just experienced the same, and it was solved as @bobinska-dev mentioned: the object of my array didn't have a name. Something like this works now:

 fields: [
    defineField({
      title: 'Features',
      type: 'array',
      name: 'features',
      of: [
        defineArrayMember({
          title: 'Feature',
          type: 'object',
          name: 'feature',
          fields: [
            defineField({
              title: 'Text',
              type: 'string',
              name: 'text',
            }),
          ],
        }),
      ],
    }),
  ]

And I can have custom validations in the document root like:

validation: (Rule: Rule) => [
  Rule.custom((module: { features?: { _key: string; text?: string }[] } | undefined, context) => {
    if (context.document?.component !== objectName) {
      return true
    }

    if (!module?.features?.length) {
      return true
    }

    const errors = module.features
      .map((feature) => {
        return feature.text ? true : { _key: feature._key }
      })
      .filter(isKeyedObject)

    return errors.length === 0
      ? true
      : {
          message: 'Field is required',
          paths: errors.map((error) => ['features', { _key: error._key }, 'text']) as unknown as Path[],
        }
  }).error(),
]

I can add a new item, while empty, the "text" receives an error, and when is filled, the error is gone. However, another issue started to happen: I get an error "Uncaught error Parent value is not an array, cannot get path segment: [_key == [object Object]]" when I remove an object from the array that had a validation problem.

fernandesGabriel avatar Jan 23 '24 16:01 fernandesGabriel

@fernandesGabriel πŸŽ‰ wonderful to hear!

  • ask these kinda of questions in the slack community 🫑

  • simplify the validation: add validation for fields in an array item on the field level (so at the text field and use context.parent and context.document to get all values needed for it)

  • make sure you add more validation return true conditions, which take the removal of an item into account (what the field validation does, so better to add these within the object definition)

Try implementing something like this example I published a while ago

I will close this issue, if there are no follow-up questions ☺️

bobinska-dev avatar Jan 23 '24 22:01 bobinska-dev