diff --git a/packages/@sanity/form-builder/src/inputs/ArrayInput/ArrayInput.js b/packages/@sanity/form-builder/src/inputs/ArrayInput/ArrayInput.js index ccad6a13cef..80b5c13f349 100644 --- a/packages/@sanity/form-builder/src/inputs/ArrayInput/ArrayInput.js +++ b/packages/@sanity/form-builder/src/inputs/ArrayInput/ArrayInput.js @@ -2,6 +2,7 @@ import React from 'react' import ArrayFunctions from 'part:@sanity/form-builder/input/array/functions' import {map} from 'rxjs/operators' +import {isPlainObject} from 'lodash' import type {Uploader} from '../../sanity/uploads/typedefs' import type {Marker, Type} from '../../typedefs' import type {Path} from '../../typedefs/path' @@ -261,6 +262,15 @@ export default class ArrayInput extends React.Component { onChange(PatchEvent.from(...patches)) } + handleRemoveNonObjectValues = () => { + const {onChange, value} = this.props + const nonObjects = value + .reduce((acc, val, i) => (isPlainObject(val) ? acc : acc.concat(i)), []) + .reverse() + const patches = nonObjects.map(index => unset([index])) + onChange(PatchEvent.from(...patches)) + } + handleUpload = ({file, type, uploader}) => { const {onChange} = this.props const item = createProtoValue(type) @@ -280,6 +290,34 @@ export default class ArrayInput extends React.Component { render() { const {type, level, markers, readOnly, onChange, value} = this.props + const hasNonObjectValues = (value || []).some(item => !isPlainObject(item)) + if (hasNonObjectValues) { + return ( +
+
+ Some items in this list are not objects. We need to remove them before the list can be + edited. +
+ +
+
Why is this happening?}> + This usually happens when items are created through an API client from outside the + Content Studio and sets invalid data, or a custom input component have inserted + incorrect values into the list. +
+
+
+ ) + } + const hasMissingKeys = (value || []).some(item => !item._key) if (hasMissingKeys) { return ( @@ -296,9 +334,7 @@ export default class ArrayInput extends React.Component { Some items in this list are missing their keys. We need to fix this before the list can be edited.
- +
Why is this happening?}> This usually happens when items are created through the API client from outside the diff --git a/packages/@sanity/form-builder/src/inputs/ArrayInput/styles/ArrayInput.css b/packages/@sanity/form-builder/src/inputs/ArrayInput/styles/ArrayInput.css index 28380f29479..c3596327d48 100644 --- a/packages/@sanity/form-builder/src/inputs/ArrayInput/styles/ArrayInput.css +++ b/packages/@sanity/form-builder/src/inputs/ArrayInput/styles/ArrayInput.css @@ -75,3 +75,12 @@ .fixMissingKeysButtonWrapper { margin: 1em 0; } + +.nonObjectsWarning { + composes: warning; + padding: 1em; +} + +.removeNonObjectsButtonWrapper { + composes: fixMissingKeysButtonWrapper; +} diff --git a/packages/@sanity/validation/src/validateDocument.js b/packages/@sanity/validation/src/validateDocument.js index 6e56d6cbca0..d7cb31ce6b2 100644 --- a/packages/@sanity/validation/src/validateDocument.js +++ b/packages/@sanity/validation/src/validateDocument.js @@ -90,7 +90,7 @@ function validateArray(items, type, path, options) { } // Validate items within array const itemChecks = items.map((item, i) => { - const pathSegment = item._key ? {_key: item._key} : i + const pathSegment = item && item._key ? {_key: item._key} : i const itemType = resolveTypeForArrayItem(item, type.of) const itemPath = appendPath(path, [pathSegment]) return validateItem(item, itemType, itemPath, { @@ -104,6 +104,17 @@ function validateArray(items, type, path, options) { } function validatePrimitive(item, type, path, options) { + if (!type) { + return [ + { + type: 'validation', + level: 'error', + path, + item: new ValidationError('Unable to resolve type for item') + } + ] + } + if (!type.validation) { return [] } @@ -118,7 +129,7 @@ function validatePrimitive(item, type, path, options) { } function resolveTypeForArrayItem(item, candidates) { - const primitive = !item._type && Type.string(item).toLowerCase() + const primitive = !item || (!item._type && Type.string(item).toLowerCase()) if (primitive) { return candidates.find(candidate => candidate.jsonType === primitive) }