Repository: mozilla-services/react-jsonschema-form
Pull request #1406 (2019-08-14 02:45:18)

Fix omitData for top level array
### Reasons for making this change

Enabling omitData currently converts a top level array to an object. Now includes a check if the form's top level type is an array, and converts back to an array if necessary.

#1396

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've [checked the rendering](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) of the Markdown text I've added
* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [ ] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) if needed
* [ ] **I'm adding a new feature**
  - [ ] I've updated the playground with an example use of the feature


epicfaace: Thanks @Tonexus ! Can you add a test?
Tonexus: Ok, added one test to make sure the top level array stays as an array.

Patch:
diff --git a/playground/app.js b/playground/app.js
index a8d8c77fd..c1d6bd8ea 100644
--- a/playground/app.js
+++ b/playground/app.js
@@ -384,12 +384,14 @@ class App extends Component {
     this.setState({ formData, shareURL: null });
 
   onShare = () => {
-    const { formData, schema, uiSchema } = this.state;
+    const { formData, schema, uiSchema, liveSettings } = this.state;
     const {
       location: { origin, pathname },
     } = document;
     try {
-      const hash = btoa(JSON.stringify({ formData, schema, uiSchema }));
+      const hash = btoa(
+        JSON.stringify({ formData, schema, uiSchema, liveSettings })
+      );
       this.setState({ shareURL: `${origin}${pathname}#${hash}` });
     } catch (err) {
       this.setState({ shareURL: null });
diff --git a/src/components/Form.js b/src/components/Form.js
index 4eacafc8d..38d11b341 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -139,7 +139,12 @@ export default class Form extends Component {
       return formData;
     }
 
-    return _pick(formData, fields);
+    let data = _pick(formData, fields);
+    if (Array.isArray(formData)) {
+      return Object.keys(data).map(key => data[key]);
+    }
+
+    return data;
   };
 
   getFieldNames = (pathSchema, formData) => {
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js
index c9bcce445..ffe291b84 100644
--- a/src/components/fields/ObjectField.js
+++ b/src/components/fields/ObjectField.js
@@ -70,6 +70,7 @@ class ObjectField extends Component {
   };
 
   state = {
+    wasPropertyKeyModified: false,
     additionalProperties: {},
   };
 
@@ -128,6 +129,7 @@ class ObjectField extends Component {
       if (oldValue === value) {
         return;
       }
+
       value = this.getAvailableKey(value, this.props.formData);
       const newFormData = { ...this.props.formData };
       const newKeys = { [oldValue]: value };
@@ -136,6 +138,9 @@ class ObjectField extends Component {
         return { [newKey]: newFormData[key] };
       });
       const renamedObj = Object.assign({}, ...keyValues);
+
+      this.setState({ wasPropertyKeyModified: true });
+
       this.props.onChange(
         renamedObj,
         errorSchema &&
@@ -191,10 +196,19 @@ class ObjectField extends Component {
       onFocus,
       registry = getDefaultRegistry(),
     } = this.props;
+
     const { definitions, fields, formContext } = registry;
     const { SchemaField, TitleField, DescriptionField } = fields;
     const schema = retrieveSchema(this.props.schema, definitions, formData);
-    const title = schema.title === undefined ? name : schema.title;
+
+    // If this schema has a title defined, but the user has set a new key/label, retain their input.
+    let title;
+    if (this.state.wasPropertyKeyModified) {
+      title = name;
+    } else {
+      title = schema.title === undefined ? name : schema.title;
+    }
+
     const description = uiSchema["ui:description"] || schema.description;
     let orderedProperties;
     try {
@@ -242,6 +256,7 @@ class ObjectField extends Component {
               idSchema={idSchema[name]}
               idPrefix={idPrefix}
               formData={(formData || {})[name]}
+              wasPropertyKeyModified={this.state.wasPropertyKeyModified}
               onKeyChange={this.onKeyChange(name)}
               onChange={this.onPropertyChange(
                 name,
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js
index 593a19868..83744135f 100644
--- a/src/components/fields/SchemaField.js
+++ b/src/components/fields/SchemaField.js
@@ -235,6 +235,7 @@ function SchemaFieldRender(props) {
     onDropPropertyClick,
     required,
     registry = getDefaultRegistry(),
+    wasPropertyKeyModified = false,
   } = props;
   const { definitions, fields, formContext } = registry;
   const FieldTemplate =
@@ -296,8 +297,15 @@ function SchemaFieldRender(props) {
 
   const { type } = schema;
   const id = idSchema.$id;
-  const label =
-    uiSchema["ui:title"] || props.schema.title || schema.title || name;
+
+  // If this schema has a title defined, but the user has set a new key/label, retain their input.
+  let label;
+  if (wasPropertyKeyModified) {
+    label = name;
+  } else {
+    label = uiSchema["ui:title"] || props.schema.title || schema.title || name;
+  }
+
   const description =
     uiSchema["ui:description"] ||
     props.schema.description ||
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index c66b8fd61..8a4b3eaaf 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -9,7 +9,7 @@ module.exports = {
   output: {
     path: path.join(__dirname, "build"),
     filename: "bundle.js",
-    publicPath: "/react-jsonschema-form/"
+    publicPath: process.env.SHOW_NETLIFY_BADGE ? "": "/react-jsonschema-form/"
   },
   plugins: [
     new MonacoWebpackPlugin({


Test patch:
diff --git a/test/Form_test.js b/test/Form_test.js
index 7084d553a..2903aef49 100644
--- a/test/Form_test.js
+++ b/test/Form_test.js
@@ -866,6 +866,25 @@ describe("Form", () => {
       expect(result).eql("foo");
     });
 
+    it("should return the root level array", () => {
+      const schema = {
+        type: "array",
+        items: {
+          type: "string",
+        },
+      };
+      const formData = [];
+      const onSubmit = sandbox.spy();
+      const { comp } = createFormComponent({
+        schema,
+        formData,
+        onSubmit,
+      });
+
+      const result = comp.getUsedFormData(formData, []);
+      expect(result).eql([]);
+    });
+
     it("should call getUsedFormData with data from fields in event", () => {
       const schema = {
         type: "object",
diff --git a/test/ObjectField_test.js b/test/ObjectField_test.js
index 3605b9702..eb41819e1 100644
--- a/test/ObjectField_test.js
+++ b/test/ObjectField_test.js
@@ -552,6 +552,26 @@ describe("ObjectField", () => {
       expect(comp.state.formData.newFirst).eql(1);
     });
 
+    it("should retain user-input data if key-value pair has a title present in the schema", () => {
+      const { comp, node } = createFormComponent({
+        schema: {
+          type: "object",
+          additionalProperties: {
+            title: "Custom title",
+            type: "string",
+          },
+        },
+        formData: { "Custom title": 1 },
+      });
+
+      const textNode = node.querySelector("#root_Custom\\ title-key");
+      Simulate.blur(textNode, {
+        target: { value: "Renamed custom title" },
+      });
+
+      expect(comp.state.formData["Renamed custom title"]).eql(1);
+    });
+
     it("should keep order of renamed key-value pairs while renaming key", () => {
       const { comp, node } = createFormComponent({
         schema,