Repository: mozilla-services/react-jsonschema-form
Pull request #1374 (2019-07-27 18:29:06)

Use monaco for playground
### Reasons for making this change

Removes a lot of the existing glitchiness with the editors.

If this is related to existing tickets, include links to them as well.

### 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
* [ ] **I'm adding or updating code**
  - [ ] 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



Patch:
diff --git a/package-lock.json b/package-lock.json
index bc6c580b4..44b490b29 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2841,6 +2841,78 @@
         }
       }
     },
+    "@types/anymatch": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
+      "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "12.6.8",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
+      "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==",
+      "dev": true
+    },
+    "@types/prop-types": {
+      "version": "15.7.1",
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
+      "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==",
+      "dev": true
+    },
+    "@types/react": {
+      "version": "16.8.23",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz",
+      "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==",
+      "dev": true,
+      "requires": {
+        "@types/prop-types": "*",
+        "csstype": "^2.2.0"
+      }
+    },
+    "@types/tapable": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz",
+      "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==",
+      "dev": true
+    },
+    "@types/uglify-js": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
+      "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==",
+      "dev": true,
+      "requires": {
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "@types/webpack": {
+      "version": "4.32.1",
+      "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.32.1.tgz",
+      "integrity": "sha512-9n38CBx9uga1FEAdTipnt0EkbKpsCJFh7xJb1LE65FFb/A6OOLFX022vYsGC1IyVCZ/GroNg9u/RMmlDxGcLIw==",
+      "dev": true,
+      "requires": {
+        "@types/anymatch": "*",
+        "@types/node": "*",
+        "@types/tapable": "*",
+        "@types/uglify-js": "*",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "@webassemblyjs/ast": {
       "version": "1.7.8",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz",
@@ -3951,7 +4023,7 @@
     "circular-json": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
-      "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=",
+      "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
       "dev": true
     },
     "clap": {
@@ -4473,6 +4545,20 @@
         "postcss-modules-scope": "^1.0.0",
         "postcss-modules-values": "^1.1.0",
         "source-list-map": "^0.1.4"
+      },
+      "dependencies": {
+        "loader-utils": {
+          "version": "0.2.17",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+          "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+          "dev": true,
+          "requires": {
+            "big.js": "^3.1.3",
+            "emojis-list": "^2.0.0",
+            "json5": "^0.5.0",
+            "object-assign": "^4.0.1"
+          }
+        }
       }
     },
     "css-selector-tokenizer": {
@@ -4556,6 +4642,12 @@
         "cssom": "0.3.x"
       }
     },
+    "csstype": {
+      "version": "2.6.6",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
+      "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==",
+      "dev": true
+    },
     "cyclist": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
@@ -6874,7 +6966,7 @@
     "inquirer": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
-      "integrity": "sha1-ndLyrXZdyrH/BEO0kUQqILoifck=",
+      "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
       "dev": true,
       "requires": {
         "ansi-escapes": "^3.0.0",
@@ -7535,15 +7627,37 @@
       "dev": true
     },
     "loader-utils": {
-      "version": "0.2.17",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
-      "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+      "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
       "dev": true,
       "requires": {
-        "big.js": "^3.1.3",
+        "big.js": "^5.2.2",
         "emojis-list": "^2.0.0",
-        "json5": "^0.5.0",
-        "object-assign": "^4.0.1"
+        "json5": "^1.0.1"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
       }
     },
     "locate-path": {
@@ -8130,6 +8244,21 @@
         }
       }
     },
+    "monaco-editor": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.17.1.tgz",
+      "integrity": "sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==",
+      "dev": true
+    },
+    "monaco-editor-webpack-plugin": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz",
+      "integrity": "sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==",
+      "dev": true,
+      "requires": {
+        "@types/webpack": "^4.4.19"
+      }
+    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -9811,7 +9940,7 @@
     "pluralize": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
-      "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=",
+      "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
       "dev": true
     },
     "posix-character-classes": {
@@ -10726,6 +10855,39 @@
       "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
       "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
     },
+    "react-monaco-editor": {
+      "version": "0.26.2",
+      "resolved": "https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.26.2.tgz",
+      "integrity": "sha512-a7/w6l8873ankpa5cdAwXSRnwEis8V/2YVeQA0JdTh0edFhQ/2TKlgm8bOFYmGX3taBk+EVp9OMNQvYH1O73iA==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*",
+        "monaco-editor": "^0.17.0",
+        "prop-types": "^15.7.2"
+      },
+      "dependencies": {
+        "loose-envify": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+          "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+          "dev": true,
+          "requires": {
+            "js-tokens": "^3.0.0 || ^4.0.0"
+          }
+        },
+        "prop-types": {
+          "version": "15.7.2",
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+          "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+          "dev": true,
+          "requires": {
+            "loose-envify": "^1.4.0",
+            "object-assign": "^4.1.1",
+            "react-is": "^16.8.1"
+          }
+        }
+      }
+    },
     "react-portal": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/react-portal/-/react-portal-4.2.0.tgz",
@@ -11757,7 +11919,7 @@
     "table": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
-      "integrity": "sha1-ozRHN1OR52atNNNIbm4q7chNLjY=",
+      "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
       "dev": true,
       "requires": {
         "ajv": "^5.2.3",
@@ -11869,7 +12031,7 @@
     "tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
       "dev": true,
       "requires": {
         "os-tmpdir": "~1.0.2"
diff --git a/package.json b/package.json
index aee557e01..de483f76c 100644
--- a/package.json
+++ b/package.json
@@ -24,8 +24,8 @@
   "prettierOptions": "--jsx-bracket-same-line --trailing-comma es5 --semi --tab-width 2",
   "lint-staged": {
     "{playground,src,test}/**/*.js": [
-      "npm run lint",
-      "npm run cs-format",
+      "eslint --fix",
+      "prettier --jsx-bracket-same-line --trailing-comma es5 --use-tabs false --semi --tab-width 2 --write",
       "git add"
     ]
   },
@@ -85,13 +85,16 @@
     "jsdom": "^8.3.0",
     "json-loader": "^0.5.7",
     "lint-staged": "^3.3.1",
+    "loader-utils": "^1.2.3",
     "mini-css-extract-plugin": "^0.4.3",
     "mocha": "^5.2.0",
+    "monaco-editor-webpack-plugin": "^1.7.0",
     "nyc": "^13.2.0",
     "prettier": "^1.15.1",
     "react": "^15.5.0",
     "react-codemirror2": "^4.1.0",
     "react-dom": "^15.3.2",
+    "react-monaco-editor": "^0.26.2",
     "react-portal": "^4.2.0",
     "react-transform-catch-errors": "^1.0.0",
     "react-transform-hmr": "^1.0.1",
diff --git a/playground/app.js b/playground/app.js
index b2a240d58..c1c8ca121 100644
--- a/playground/app.js
+++ b/playground/app.js
@@ -1,25 +1,12 @@
 import React, { Component } from "react";
 import { render } from "react-dom";
-import { UnControlled as CodeMirror } from "react-codemirror2";
-import "codemirror/mode/javascript/javascript";
+import MonacoEditor from "react-monaco-editor";
 
 import { shouldRender } from "../src/utils";
 import { samples } from "./samples";
 import Form from "../src";
 
-// Import a few CodeMirror themes; these are used to match alternative
-// bootstrap ones.
-import "codemirror/lib/codemirror.css";
-import "codemirror/theme/dracula.css";
-import "codemirror/theme/blackboard.css";
-import "codemirror/theme/mbo.css";
-import "codemirror/theme/ttcn.css";
-import "codemirror/theme/solarized.css";
-import "codemirror/theme/monokai.css";
-import "codemirror/theme/eclipse.css";
-
 const log = type => console.log.bind(console, type);
-const fromJson = json => JSON.parse(json);
 const toJson = val => JSON.stringify(val, null, 2);
 const liveSettingsSchema = {
   type: "object",
@@ -30,20 +17,6 @@ const liveSettingsSchema = {
     liveOmit: { type: "boolean", title: "Live omit" },
   },
 };
-const cmOptions = {
-  theme: "default",
-  height: "auto",
-  viewportMargin: Infinity,
-  mode: {
-    name: "javascript",
-    json: true,
-    statementIndent: 2,
-  },
-  lineNumbers: true,
-  lineWrapping: true,
-  indentWithTabs: false,
-  tabSize: 2,
-};
 const themes = {
   default: {
     stylesheet:
@@ -133,6 +106,13 @@ const themes = {
   },
 };
 
+const monacoEditorOptions = {
+  minimap: {
+    enabled: false,
+  },
+  automaticLayout: true,
+};
+
 class GeoPosition extends Component {
   constructor(props) {
     super(props);
@@ -194,22 +174,28 @@ class Editor extends Component {
   }
 
   shouldComponentUpdate(nextProps, nextState) {
-    return shouldRender(this, nextProps, nextState);
+    if (this.state.valid) {
+      return (
+        JSON.stringify(JSON.parse(nextProps.code)) !==
+        JSON.stringify(JSON.parse(this.state.code))
+      );
+    }
+    return false;
   }
 
-  onCodeChange = (editor, metadata, code) => {
-    this.setState({ valid: true, code });
-    setImmediate(() => {
-      try {
-        this.props.onChange(fromJson(this.state.code));
-      } catch (err) {
-        this.setState({ valid: false, code });
-      }
-    });
+  onCodeChange = code => {
+    try {
+      const parsedCode = JSON.parse(code);
+      this.setState({ valid: true, code }, () =>
+        this.props.onChange(parsedCode)
+      );
+    } catch (err) {
+      this.setState({ valid: false, code });
+    }
   };
 
   render() {
-    const { title, theme } = this.props;
+    const { title } = this.props;
     const icon = this.state.valid ? "ok" : "remove";
     const cls = this.state.valid ? "valid" : "invalid";
     return (
@@ -218,11 +204,13 @@ class Editor extends Component {
           <span className={`${cls} glyphicon glyphicon-${icon}`} />
           {" " + title}
         </div>
-        <CodeMirror
+        <MonacoEditor
+          language="json"
           value={this.state.code}
+          theme="vs-light"
           onChange={this.onCodeChange}
-          autoCursor={false}
-          options={Object.assign({}, cmOptions, { theme })}
+          height={400}
+          options={monacoEditorOptions}
         />
       </div>
     );
diff --git a/src/components/Form.js b/src/components/Form.js
index 9201bcd62..4eacafc8d 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -13,6 +13,7 @@ import {
   getDefaultRegistry,
   deepEquals,
   toPathSchema,
+  isObject,
 } from "../utils";
 import validateFormData, { toErrorList } from "../validate";
 
@@ -30,7 +31,7 @@ export default class Form extends Component {
 
   constructor(props) {
     super(props);
-    this.state = this.getStateFromProps(props);
+    this.state = this.getStateFromProps(props, props.formData);
     if (
       this.props.onChange &&
       !deepEquals(this.state.formData, this.props.formData)
@@ -41,7 +42,7 @@ export default class Form extends Component {
   }
 
   componentWillReceiveProps(nextProps) {
-    const nextState = this.getStateFromProps(nextProps);
+    const nextState = this.getStateFromProps(nextProps, nextProps.formData);
     if (
       !deepEquals(nextState.formData, nextProps.formData) &&
       !deepEquals(nextState.formData, this.state.formData) &&
@@ -52,7 +53,7 @@ export default class Form extends Component {
     this.setState(nextState);
   }
 
-  getStateFromProps(props, inputFormData = props.formData) {
+  getStateFromProps(props, inputFormData) {
     const state = this.state || {};
     const schema = "schema" in props ? props.schema : this.props.schema;
     const uiSchema = "uiSchema" in props ? props.uiSchema : this.props.uiSchema;
@@ -171,6 +172,10 @@ export default class Form extends Component {
   };
 
   onChange = (formData, newErrorSchema) => {
+    if (isObject(formData) || Array.isArray(formData)) {
+      const newState = this.getStateFromProps(this.props, formData);
+      formData = newState.formData;
+    }
     const mustValidate = !this.props.noValidate && this.props.liveValidate;
     let state = { formData };
     let newFormData = formData;
diff --git a/src/utils.js b/src/utils.js
index 2099c0412..44ef93834 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -148,7 +148,8 @@ function computeDefaults(
   schema,
   parentDefaults,
   definitions,
-  rawFormData = {}
+  rawFormData = {},
+  includeUndefinedValues = false
 ) {
   const formData = isObject(rawFormData) ? rawFormData : {};
   // Compute the defaults recursively: give highest priority to deepest nodes.
@@ -163,13 +164,31 @@ function computeDefaults(
   } else if ("$ref" in schema) {
     // Use referenced schema defaults for this node.
     const refSchema = findSchemaDefinition(schema.$ref, definitions);
-    return computeDefaults(refSchema, defaults, definitions, formData);
+    return computeDefaults(
+      refSchema,
+      defaults,
+      definitions,
+      formData,
+      includeUndefinedValues
+    );
   } else if ("dependencies" in schema) {
     const resolvedSchema = resolveDependencies(schema, definitions, formData);
-    return computeDefaults(resolvedSchema, defaults, definitions, formData);
+    return computeDefaults(
+      resolvedSchema,
+      defaults,
+      definitions,
+      formData,
+      includeUndefinedValues
+    );
   } else if (isFixedItems(schema)) {
     defaults = schema.items.map(itemSchema =>
-      computeDefaults(itemSchema, undefined, definitions, formData)
+      computeDefaults(
+        itemSchema,
+        undefined,
+        definitions,
+        formData,
+        includeUndefinedValues
+      )
     );
   } else if ("oneOf" in schema) {
     schema =
@@ -190,12 +209,16 @@ function computeDefaults(
       return Object.keys(schema.properties || {}).reduce((acc, key) => {
         // Compute the defaults for this node, with the parent defaults we might
         // have from a previous run: defaults[key].
-        acc[key] = computeDefaults(
+        let computedDefault = computeDefaults(
           schema.properties[key],
           (defaults || {})[key],
           definitions,
-          (formData || {})[key]
+          (formData || {})[key],
+          includeUndefinedValues
         );
+        if (includeUndefinedValues || computedDefault !== undefined) {
+          acc[key] = computedDefault;
+        }
         return acc;
       }, {});
 
@@ -225,7 +248,12 @@ function computeDefaults(
   return defaults;
 }
 
-export function getDefaultFormState(_schema, formData, definitions = {}) {
+export function getDefaultFormState(
+  _schema,
+  formData,
+  definitions = {},
+  includeUndefinedValues = false
+) {
   if (!isObject(_schema)) {
     throw new Error("Invalid schema: " + _schema);
   }
@@ -234,7 +262,8 @@ export function getDefaultFormState(_schema, formData, definitions = {}) {
     schema,
     _schema.default,
     definitions,
-    formData
+    formData,
+    includeUndefinedValues
   );
   if (typeof formData === "undefined") {
     // No form data? Use schema defaults.
@@ -244,6 +273,9 @@ export function getDefaultFormState(_schema, formData, definitions = {}) {
     // Override schema defaults with form data.
     return mergeObjects(defaults, formData);
   }
+  if (formData === 0) {
+    return formData;
+  }
   return formData || defaults;
 }
 
diff --git a/src/validate.js b/src/validate.js
index 8454e93e9..e70aebfee 100644
--- a/src/validate.js
+++ b/src/validate.js
@@ -1,7 +1,7 @@
 import toPath from "lodash/toPath";
 import Ajv from "ajv";
 let ajv = createAjvInstance();
-import { deepEquals } from "./utils";
+import { deepEquals, getDefaultFormState } from "./utils";
 
 let formerCustomFormats = null;
 let formerMetaSchema = null;
@@ -172,6 +172,10 @@ export default function validateFormData(
   additionalMetaSchemas = [],
   customFormats = {}
 ) {
+  // Include form data with undefined values, which is required for validation.
+  const { definitions } = schema;
+  formData = getDefaultFormState(schema, formData, definitions, true);
+
   const newMetaSchemas = !deepEquals(formerMetaSchema, additionalMetaSchemas);
   const newFormats = !deepEquals(formerCustomFormats, customFormats);
 
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index 567194bfb..853733f93 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -1,5 +1,6 @@
 var path = require("path");
 var webpack = require("webpack");
+const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
 
 module.exports = {
   mode: "development",
@@ -14,6 +15,9 @@ module.exports = {
     publicPath: "/static/"
   },
   plugins: [
+    new MonacoWebpackPlugin({
+      languages: ['json']
+    }),
     new webpack.HotModuleReplacementPlugin(),
   ],
   module: {
diff --git a/webpack.config.dist.js b/webpack.config.dist.js
index 85f2dbb67..37d1f7d21 100644
--- a/webpack.config.dist.js
+++ b/webpack.config.dist.js
@@ -1,5 +1,6 @@
 var path = require("path");
 var webpack = require("webpack");
+const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
 
 module.exports = {
   mode: "production",
@@ -14,6 +15,9 @@ module.exports = {
     libraryTarget: "umd"
   },
   plugins: [
+    new MonacoWebpackPlugin({
+      languages: ['json']
+    }),
     new webpack.DefinePlugin({
       "process.env": {
         NODE_ENV: JSON.stringify("production")
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index 4911eb392..34f0da4f5 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -1,6 +1,7 @@
 var path = require("path");
 var webpack = require("webpack");
 var MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
 
 module.exports = {
   mode: "production",
@@ -11,6 +12,9 @@ module.exports = {
     publicPath: "/static/"
   },
   plugins: [
+    new MonacoWebpackPlugin({
+      languages: ['json']
+    }),
     new MiniCssExtractPlugin({filename: "styles.css", allChunks: true}),
     new webpack.DefinePlugin({
       "process.env": {


Test patch:
diff --git a/test/Form_test.js b/test/Form_test.js
index 1060274b1..7084d553a 100644
--- a/test/Form_test.js
+++ b/test/Form_test.js
@@ -2469,4 +2469,28 @@ describe("Form", () => {
       sinon.assert.notCalled(outerOnSubmit);
     });
   });
+
+  describe("Dependency defaults", () => {
+    it("should show dependency defaults for uncontrolled components", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          firstName: { type: "string" },
+        },
+        dependencies: {
+          firstName: {
+            properties: {
+              lastName: { type: "string", default: "Norris" },
+            },
+          },
+        },
+      };
+      const { node } = createFormComponent({ schema });
+
+      Simulate.change(node.querySelector("#root_firstName"), {
+        target: { value: "Chuck" },
+      });
+      expect(node.querySelector("#root_lastName").value).eql("Norris");
+    });
+  });
 });
diff --git a/test/SchemaField_test.js b/test/SchemaField_test.js
index 6b3cdfeff..7210ab955 100644
--- a/test/SchemaField_test.js
+++ b/test/SchemaField_test.js
@@ -304,7 +304,7 @@ describe("SchemaField", () => {
       }
     }
 
-    it("should render it's own errors", () => {
+    it("should render its own errors", () => {
       const { node } = createFormComponent({
         schema,
         uiSchema,
diff --git a/test/utils_test.js b/test/utils_test.js
index 56ed93ad0..d9210af1d 100644
--- a/test/utils_test.js
+++ b/test/utils_test.js
@@ -35,6 +35,33 @@ describe("utils", () => {
           })
         ).to.eql("foo");
       });
+
+      it("should keep existing form data that is equal to 0", () => {
+        expect(
+          getDefaultFormState(
+            {
+              type: "number",
+              default: 1,
+            },
+            0
+          )
+        ).to.eql(0);
+      });
+
+      const noneValues = [null, undefined, NaN];
+      noneValues.forEach(noneValue => {
+        it("should overwrite existing form data that is equal to a none value", () => {
+          expect(
+            getDefaultFormState(
+              {
+                type: "number",
+                default: 1,
+              },
+              noneValue
+            )
+          ).to.eql(1);
+        });
+      });
     });
 
     describe("nested default", () => {
@@ -723,6 +750,27 @@ describe("utils", () => {
         });
       });
     });
+
+    describe("with schema keys not defined in the formData", () => {
+      it("shouldn't add in undefined keys to formData", () => {
+        const schema = {
+          type: "object",
+          properties: {
+            foo: { type: "string" },
+            bar: { type: "string" },
+          },
+        };
+        const formData = {
+          foo: "foo",
+          baz: "baz",
+        };
+        const result = {
+          foo: "foo",
+          baz: "baz",
+        };
+        expect(getDefaultFormState(schema, formData)).to.eql(result);
+      });
+    });
   });
 
   describe("asNumber()", () => {
diff --git a/test/validate_test.js b/test/validate_test.js
index 8e61d0174..e118c075d 100644
--- a/test/validate_test.js
+++ b/test/validate_test.js
@@ -282,62 +282,6 @@ describe("Validation", () => {
       });
     });
 
-    describe("toErrorList()", () => {
-      it("should convert an errorSchema into a flat list", () => {
-        expect(
-          toErrorList({
-            __errors: ["err1", "err2"],
-            a: {
-              b: {
-                __errors: ["err3", "err4"],
-              },
-            },
-            c: {
-              __errors: ["err5"],
-            },
-          })
-        ).eql([
-          { stack: "root: err1" },
-          { stack: "root: err2" },
-          { stack: "b: err3" },
-          { stack: "b: err4" },
-          { stack: "c: err5" },
-        ]);
-      });
-    });
-
-    describe("transformErrors", () => {
-      const illFormedKey = "bar.'\"[]()=+*&^%$#@!";
-      const schema = {
-        type: "object",
-        properties: {
-          foo: { type: "string" },
-          [illFormedKey]: { type: "string" },
-        },
-      };
-      const newErrorMessage = "Better error message";
-      const transformErrors = errors => {
-        return [Object.assign({}, errors[0], { message: newErrorMessage })];
-      };
-
-      let errors;
-
-      beforeEach(() => {
-        const result = validateFormData(
-          { foo: 42, [illFormedKey]: 41 },
-          schema,
-          undefined,
-          transformErrors
-        );
-        errors = result.errors;
-      });
-
-      it("should use transformErrors function", () => {
-        expect(errors).not.to.be.empty;
-        expect(errors[0].message).to.equal(newErrorMessage);
-      });
-    });
-
     describe("Invalid schema", () => {
       const schema = {
         type: "object",
@@ -376,6 +320,62 @@ describe("Validation", () => {
     });
   });
 
+  describe("toErrorList()", () => {
+    it("should convert an errorSchema into a flat list", () => {
+      expect(
+        toErrorList({
+          __errors: ["err1", "err2"],
+          a: {
+            b: {
+              __errors: ["err3", "err4"],
+            },
+          },
+          c: {
+            __errors: ["err5"],
+          },
+        })
+      ).eql([
+        { stack: "root: err1" },
+        { stack: "root: err2" },
+        { stack: "b: err3" },
+        { stack: "b: err4" },
+        { stack: "c: err5" },
+      ]);
+    });
+  });
+
+  describe("transformErrors", () => {
+    const illFormedKey = "bar.'\"[]()=+*&^%$#@!";
+    const schema = {
+      type: "object",
+      properties: {
+        foo: { type: "string" },
+        [illFormedKey]: { type: "string" },
+      },
+    };
+    const newErrorMessage = "Better error message";
+    const transformErrors = errors => {
+      return [Object.assign({}, errors[0], { message: newErrorMessage })];
+    };
+
+    let errors;
+
+    beforeEach(() => {
+      const result = validateFormData(
+        { foo: 42, [illFormedKey]: 41 },
+        schema,
+        undefined,
+        transformErrors
+      );
+      errors = result.errors;
+    });
+
+    it("should use transformErrors function", () => {
+      expect(errors).not.to.be.empty;
+      expect(errors[0].message).to.equal(newErrorMessage);
+    });
+  });
+
   describe("Form integration", () => {
     let sandbox;