Repository: mozilla-services/react-jsonschema-form
Pull request #1213 (2019-03-08 18:41:27)

Basic support for 'nullable' types
### Reasons for making this change

Modifies the implementation of `getSchemaType` so that when a property has a type of `[<any>, 'null']`, then `<any>` will be passed. This allows the library to render and validate the field correctly. For the sake of simplicity, this change does not attempt any further assumptions, coercions or transformations.

References #465 

### Checklist

* [x] **I'm updating documentation**
  - [x] 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
  - [x] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) 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
* [x] **I'm adding a new feature**
  - [x] I've updated the playground with an example use of the feature


warrenseymour: I wanted to land this PR in draft status before the end of the week in the hope that it'll get some eyes over the weekend. If this feature is desirable to the project maintainers, I'll add some tests and a Playground example when I return on Monday.

Patch:
diff --git a/docs/index.md b/docs/index.md
index 4c26d8645..07b63049b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -281,6 +281,8 @@ $ git push --tags origin master
 A: The `anyOf`  and `oneOf` keywords are supported,  however, properties declared inside the `anyOf/oneOf` should not overlap with properties "outside" of the `anyOf/oneOf`.
 There is also special cased where you can use `oneOf` in [schema dependencies](dependencies.md#schema-dependencies), If you'd like to help improve support for these keywords, see the following issues for inspiration [#329](https://github.com/mozilla-services/react-jsonschema-form/pull/329) or [#417](https://github.com/mozilla-services/react-jsonschema-form/pull/417). See also: [#52](https://github.com/mozilla-services/react-jsonschema-form/issues/52), [#151](https://github.com/mozilla-services/react-jsonschema-form/issues/151), [#171](https://github.com/mozilla-services/react-jsonschema-form/issues/171), [#200](https://github.com/mozilla-services/react-jsonschema-form/issues/200), [#282](https://github.com/mozilla-services/react-jsonschema-form/issues/282), [#302](https://github.com/mozilla-services/react-jsonschema-form/pull/302), [#330](https://github.com/mozilla-services/react-jsonschema-form/issues/330), [#430](https://github.com/mozilla-services/react-jsonschema-form/issues/430), [#522](https://github.com/mozilla-services/react-jsonschema-form/issues/522), [#538](https://github.com/mozilla-services/react-jsonschema-form/issues/538), [#551](https://github.com/mozilla-services/react-jsonschema-form/issues/551), [#552](https://github.com/mozilla-services/react-jsonschema-form/issues/552), or [#648](https://github.com/mozilla-services/react-jsonschema-form/issues/648).
 
+In addition, "nullable" types are supported in a narrow sense: If a property declares a type of `["<some-type>", "null"]`, then `"some-type"` will be passed through as the type used to determine which widget to use for rendering the field. However, the actual rendering and handling of the field is unchanged; you are free to handle this using an approach (`'ui:emptyValue': null`, for example) best-suited to your use case.
+
 ### Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]?
 
 A: Probably not. We use Bootstrap v3 and it works fine for our needs. We would like for react-jsonschema-form to support other frameworks, we just don't want to support them ourselves. Ideally, these frontend styles could be added to react-jsonschema-form with a third-party library. If there is a technical limitation preventing this, please consider opening a PR. See also: [#91](https://github.com/mozilla-services/react-jsonschema-form/issues/91), [#99](https://github.com/mozilla-services/react-jsonschema-form/issues/99), [#125](https://github.com/mozilla-services/react-jsonschema-form/issues/125), [#237](https://github.com/mozilla-services/react-jsonschema-form/issues/237), [#287](https://github.com/mozilla-services/react-jsonschema-form/issues/287), [#299](https://github.com/mozilla-services/react-jsonschema-form/issues/299), [#440](https://github.com/mozilla-services/react-jsonschema-form/issues/440), [#461](https://github.com/mozilla-services/react-jsonschema-form/issues/461), [#546](https://github.com/mozilla-services/react-jsonschema-form/issues/546), [#555](https://github.com/mozilla-services/react-jsonschema-form/issues/555), [#626](https://github.com/mozilla-services/react-jsonschema-form/issues/626), and [#623](https://github.com/mozilla-services/react-jsonschema-form/pull/623).
diff --git a/playground/samples/index.js b/playground/samples/index.js
index 4f5f9346e..2abe94452 100644
--- a/playground/samples/index.js
+++ b/playground/samples/index.js
@@ -20,6 +20,7 @@ import alternatives from "./alternatives";
 import propertyDependencies from "./propertyDependencies";
 import schemaDependencies from "./schemaDependencies";
 import additionalProperties from "./additionalProperties";
+import nullable from "./nullable";
 
 export const samples = {
   Simple: simple,
@@ -44,4 +45,5 @@ export const samples = {
   "Additional Properties": additionalProperties,
   "Any Of": anyOf,
   "One Of": oneOf,
+  Nullable: nullable,
 };
diff --git a/playground/samples/nullable.js b/playground/samples/nullable.js
new file mode 100644
index 000000000..1068ac974
--- /dev/null
+++ b/playground/samples/nullable.js
@@ -0,0 +1,72 @@
+module.exports = {
+  schema: {
+    title: "A registration form (nullable)",
+    description: "A simple form example using nullable types",
+    type: "object",
+    required: ["firstName", "lastName"],
+    properties: {
+      firstName: {
+        type: "string",
+        title: "First name",
+        default: "Chuck",
+      },
+      lastName: {
+        type: "string",
+        title: "Last name",
+      },
+      age: {
+        type: "integer",
+        title: "Age",
+      },
+      bio: {
+        type: ["string", "null"],
+        title: "Bio",
+      },
+      password: {
+        type: "string",
+        title: "Password",
+        minLength: 3,
+      },
+      telephone: {
+        type: "string",
+        title: "Telephone",
+        minLength: 10,
+      },
+    },
+  },
+  uiSchema: {
+    firstName: {
+      "ui:autofocus": true,
+      "ui:emptyValue": "",
+    },
+    age: {
+      "ui:widget": "updown",
+      "ui:title": "Age of person",
+      "ui:description": "(earthian year)",
+    },
+    bio: {
+      "ui:widget": "textarea",
+      "ui:placeholder":
+        "Leaving this field empty will cause formData property to be `null`",
+      "ui:emptyValue": null,
+    },
+    password: {
+      "ui:widget": "password",
+      "ui:help": "Hint: Make it strong!",
+    },
+    date: {
+      "ui:widget": "alt-datetime",
+    },
+    telephone: {
+      "ui:options": {
+        inputType: "tel",
+      },
+    },
+  },
+  formData: {
+    lastName: "Norris",
+    age: 75,
+    bio: null,
+    password: "noneed",
+  },
+};
diff --git a/playground/samples/propertyDependencies.js b/playground/samples/propertyDependencies.js
index 133f708ea..efaba6e68 100644
--- a/playground/samples/propertyDependencies.js
+++ b/playground/samples/propertyDependencies.js
@@ -1,5 +1,3 @@
-import React from "react";
-
 module.exports = {
   schema: {
     title: "Property dependencies",
@@ -56,12 +54,8 @@ module.exports = {
   uiSchema: {
     unidirectional: {
       credit_card: {
-        "ui:help": (
-          <p>
-            If you enter anything here then <code>billing_address</code> will
-            become required.
-          </p>
-        ),
+        "ui:help":
+          "If you enter anything here then billing_address will become required.",
       },
       billing_address: {
         "ui:help":
@@ -70,20 +64,12 @@ module.exports = {
     },
     bidirectional: {
       credit_card: {
-        "ui:help": (
-          <p>
-            "If you enter anything here then <code>billing_address</code> will
-            become required.
-          </p>
-        ),
+        "ui:help":
+          "If you enter anything here then billing_address will become required.",
       },
       billing_address: {
-        "ui:help": (
-          <p>
-            If you enter anything here then <code>credit_card</code> will become
-            required.
-          </p>
-        ),
+        "ui:help":
+          "If you enter anything here then credit_card will become required.",
       },
     },
   },
diff --git a/playground/samples/schemaDependencies.js b/playground/samples/schemaDependencies.js
index 610d6dc63..90b8478c2 100644
--- a/playground/samples/schemaDependencies.js
+++ b/playground/samples/schemaDependencies.js
@@ -1,5 +1,3 @@
-import React from "react";
-
 module.exports = {
   schema: {
     title: "Schema dependencies",
@@ -110,12 +108,8 @@ module.exports = {
   uiSchema: {
     simple: {
       credit_card: {
-        "ui:help": (
-          <p>
-            If you enter anything here then <code>billing_address</code> will be
-            dynamically added to the form.
-          </p>
-        ),
+        "ui:help":
+          "If you enter anything here then billing_address will be dynamically added to the form.",
       },
     },
     conditional: {
diff --git a/src/utils.js b/src/utils.js
index d55386cf9..3445ad810 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -73,8 +73,13 @@ export function getSchemaType(schema) {
   }
 
   if (!type && schema.enum) {
-    type = "string";
+    return "string";
+  }
+
+  if (type instanceof Array && type.length === 2 && type.includes("null")) {
+    return type.find(type => type !== "null");
   }
+
   return type;
 }
 


Test patch:
diff --git a/test/utils_test.js b/test/utils_test.js
index 5b84d0108..49f2fb632 100644
--- a/test/utils_test.js
+++ b/test/utils_test.js
@@ -1485,6 +1485,18 @@ describe("utils", () => {
         schema: { const: 1 },
         expected: "number",
       },
+      {
+        schema: { type: ["string", "null"] },
+        expected: "string",
+      },
+      {
+        schema: { type: ["null", "number"] },
+        expected: "number",
+      },
+      {
+        schema: { type: ["integer", "null"] },
+        expected: "integer",
+      },
     ];
 
     it("should correctly guess the type of a schema", () => {