Repository: mozilla-services/react-jsonschema-form
Pull request #663 (2017-08-09 15:22:45)

Fix behaviour of arrays with minItems and default
### Reasons for making this change

When providing both default (as a list) and minItems to a field of type array
the defaults are treated as a single value that ends up in each field.

This is a repost of PR #655 which got accidentally deleted and automatically closed, and updated to fix conflict with master.

Relates to ticket
#631

### Expected behavior

defaults should be treated as a list and be given one entry each, i.e.:

When minItems > default.length provide empty or item-level default fields after the fields with the default values.
When minItems <= default.length provide one field per value in default list.

### Actual behavior

The default items gets mashed together into each field

### Checklist

* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [x] I've updated docs if needed
  - [x] I've run `npm run cs-format` on my branch to conform my code to [prettier](https://github.com/prettier/prettier) coding style
* [ ] **I'm adding a new feature**
  - [x] I've updated the playground with an example use of the feature



Patch:
diff --git a/playground/samples/arrays.js b/playground/samples/arrays.js
index c4142a3eb..e11313189 100644
--- a/playground/samples/arrays.js
+++ b/playground/samples/arrays.js
@@ -57,6 +57,16 @@ module.exports = {
           $ref: "#/definitions/Thing",
         },
       },
+      defaultsAndMinItems: {
+        type: "array",
+        title: "List and item level defaults",
+        minItems: 5,
+        default: ["carp", "trout", "bream"],
+        items: {
+          type: "string",
+          default: "unidentified",
+        },
+      },
       nestedList: {
         type: "array",
         title: "Nested list",
diff --git a/src/utils.js b/src/utils.js
index 7359dfc2f..0c24acf71 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -141,9 +141,19 @@ function computeDefaults(schema, parentDefaults, definitions = {}) {
     case "array":
       if (schema.minItems) {
         if (!isMultiSelect(schema, definitions)) {
-          return new Array(schema.minItems).fill(
-            computeDefaults(schema.items, defaults, definitions)
-          );
+          const defaultsLength = defaults ? defaults.length : 0;
+          if (schema.minItems > defaultsLength) {
+            const defaultEntries = defaults || [];
+            // populate the array with the defaults
+            const fillerEntries = new Array(
+              schema.minItems - defaultsLength
+            ).fill(
+              computeDefaults(schema.items, schema.items.defaults, definitions)
+            );
+            // then fill up the rest with either the item default or empty, up to minItems
+
+            return defaultEntries.concat(fillerEntries);
+          }
         } else {
           return [];
         }


Test patch:
diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js
index 02b023af5..07db80505 100644
--- a/test/ArrayField_test.js
+++ b/test/ArrayField_test.js
@@ -410,6 +410,76 @@ describe("ArrayField", () => {
       expect(inputs[1].value).eql("Default name");
     });
 
+    it("should render an input for each default value, even when this is greater than minItems", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          turtles: {
+            type: "array",
+            minItems: 2,
+            default: ["Raphael", "Michaelangelo", "Donatello", "Leonardo"],
+            items: {
+              type: "string",
+            },
+          },
+        },
+      };
+      const { node } = createFormComponent({ schema: schema });
+      const inputs = node.querySelectorAll("input[type=text]");
+      expect(inputs.length).to.eql(4);
+      expect(inputs[0].value).to.eql("Raphael");
+      expect(inputs[1].value).to.eql("Michaelangelo");
+      expect(inputs[2].value).to.eql("Donatello");
+      expect(inputs[3].value).to.eql("Leonardo");
+    });
+
+    it("should render enough input to match minItems, populating the first with default values, and the rest empty", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          turtles: {
+            type: "array",
+            minItems: 4,
+            default: ["Raphael", "Michaelangelo"],
+            items: {
+              type: "string",
+            },
+          },
+        },
+      };
+      const { node } = createFormComponent({ schema });
+      const inputs = node.querySelectorAll("input[type=text]");
+      expect(inputs.length).to.eql(4);
+      expect(inputs[0].value).to.eql("Raphael");
+      expect(inputs[1].value).to.eql("Michaelangelo");
+      expect(inputs[2].value).to.eql("");
+      expect(inputs[3].value).to.eql("");
+    });
+
+    it("should render enough input to match minItems, populating the first with default values, and the rest with the item default", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          turtles: {
+            type: "array",
+            minItems: 4,
+            default: ["Raphael", "Michaelangelo"],
+            items: {
+              type: "string",
+              default: "Unknown",
+            },
+          },
+        },
+      };
+      const { node } = createFormComponent({ schema });
+      const inputs = node.querySelectorAll("input[type=text]");
+      expect(inputs.length).to.eql(4);
+      expect(inputs[0].value).to.eql("Raphael");
+      expect(inputs[1].value).to.eql("Michaelangelo");
+      expect(inputs[2].value).to.eql("Unknown");
+      expect(inputs[3].value).to.eql("Unknown");
+    });
+
     it("should not add minItems extra formData entries when schema item is a multiselect", () => {
       const schema = {
         type: "object",