diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f8fb1..2634eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.14.3 + +- FIX: On removing an array item, corresponding errors must also be spliced. + # 0.14.2 - FIX: remove() removes all errors, not just the removed field. diff --git a/package.json b/package.json index 065c32b..9c94dd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mobx-autoform", - "version": "0.14.2", + "version": "0.14.3", "description": "Ridiculously simple form state management with mobx", "type": "module", "main": "dist/cjs/index.js", diff --git a/src/index.js b/src/index.js index 5321eed..c36c50f 100644 --- a/src/index.js +++ b/src/index.js @@ -39,6 +39,26 @@ let defaultGetSnapshot = form => F.flattenObject(toJS(gatherFormValues(form))) let defaultGetNestedSnapshot = form => F.unflattenObject(form.getSnapshot()) +// Splice the form errors when a array item is removed +const spliceErrors = (errors, parentPath, nodePosition) => { + const parent = parentPath.join('.') + const parentLength = parentPath.length + const arrayErrors = pickByPrefixes([parent], errors) + const newErrors = omitByPrefixes([parent], errors) + for (const [key, value] of Object.entries(arrayErrors)) { + const keyFields = key.split('.') + const idx = parseInt(keyFields[parentLength]) + if (idx > nodePosition) { + keyFields.splice(parentLength, 1, idx - 1) + const newKey = keyFields.join('.') + newErrors[newKey] = value + } else if (idx < nodePosition) { + newErrors[key] = value + } + } + return newErrors +} + export default ({ submit: configSubmit, value = {}, @@ -110,15 +130,18 @@ export default ({ remove() { let parent = form.getField(_.dropRight(1, rootPath)) || form // If array field, remove the value and the reaction will take care of the rest - if (parent[keys.itemField]) parent.value.splice(node.field, 1) + if (parent[keys.itemField]) { + parent.value.splice(node.field, 1) + state.errors = spliceErrors(state.errors, parent.path, node.field) + } // Remove object field else { node.dispose() F.unsetOn(node.field, parent.value) F.unsetOn(node.field, parent[keys.fields]) + // Clean errors for this field and all subfields + state.errors = omitByPrefixes([dotPath], state.errors, node.field) } - // Clean errors for this field and all subfields - state.errors = omitByPrefixes([dotPath], state.errors) }, }) node.path = rootPath diff --git a/src/index.test.js b/src/index.test.js index d316617..599adf9 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -200,6 +200,76 @@ describe('Methods and computeds', () => { }) }) + it('errors on remove array item', () => { + const value = { + location: { + 'country.state': { zip: '07016' }, + addresses: [ + { street: 'Meridian0', tenants: ['John00', 'John01', 'John02'] }, + { street: 'Meridian1', tenants: ['John1'] }, + { street: 'Meridian2', tenants: ['John20', 'John21', 'John22'] }, + { street: 'Meridian3', tenants: ['John30', 'John31'] }, + ], + }, + } + const fields = { + location: { + fields: { + 'country.state': { + label: 'Dotted field name', + fields: { + zip: { + validator: () => 'Invalid zip', + }, + name: {}, + }, + }, + addresses: { + label: 'Array field', + itemField: { + label: 'Item field is a record', + fields: { + street: {}, + tenants: { + label: 'Array field', + itemField: { + label: 'Item field is a primitive', + validator: arg => `Invalid tenant ${arg}`, + }, + }, + }, + }, + }, + }, + }, + } + form = Form({ fields, value }) + form.validate() + expect(_.size(form.errors)).toBe(10) + + const tenentKey = 'location.addresses.2.tenants.1' + expect(form.errors[tenentKey]).toBe('Invalid tenant John21') + form.getField(tenentKey).remove() + expect(form.errors['location.addresses.2.tenants.0']).toBe( + 'Invalid tenant John20' + ) + // tenants.2 has moved up to tenants.1 + expect(form.getField(tenentKey).value).toBe('John22') + expect(form.errors['location.addresses.2.tenants.1']).toBe( + 'Invalid tenant John22' + ) + expect(form.errors['location.addresses.2.tenants.2']).toBeUndefined() + + const addressKey = 'location.addresses.1' + form.getField(addressKey).remove() + // address.2 has moved up to address.1 + expect(form.getField('location.addresses.1.tenants.0').value).toBe('John20') + expect(form.errors['location.addresses.2.tenants.1']).toBe( + 'Invalid tenant John31' + ) + expect(form.errors['location.addresses.2.tenants.2']).toBeUndefined() + }) + describe('validate()', () => { it('field', () => { let name = form.getField('location.addresses.0.tenants.0')