Repository: mozilla-services/react-jsonschema-form
Pull request #539 (2017-04-06 14:20:57)

removing content from text field result in empty string instead of undefined
### Reasons for making this change
As mentioned here before https://github.com/mozilla-services/react-jsonschema-form/pull/476#issuecomment-292175013
The new way of handling empty text fields (setting them to undefined) is undesirable as most of the time we want to keep an empty value.
If we want to delete a field we can do so after the form validation by for emptiness and delete accordingly.

Behavior change:
- sets text field to `""` instead of undefined when you remove everything inside it 

### Checklist

* [x] **I'm updating documentation**
  - [x] I've checked the rendering 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 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

Please take in consideration that I'm fairly new to the repo and might have underestimated the change, I'd like your input if that's the case!

Quentin

n1k0: I'm not sure we want to revert the old behavior. We spent weeks (months) aligning on this and it was hard to reach consensus. Please ensure reading comments in related issues, there are a lot of constraints which may not be obvious at first glance. 

Could you please describe a little more your specific use case so we can ensure we're not missing any piece of valuable information here?

Calling @crumblix @olzraiti @revolunet @ @Reggino @knilink @vinhlh @mplis-jetsetter @glasserc @MoOx and other people interested in this project to provide feedback.
quentin-sommer: Hi,

When implementing data edit forms and dealing with strings I think we have 2 use cases
- string is used to hold a link information (such as an id to another resource or an enum value).
In this case, it's easier to mark the emptiness of the field as `null` or `undefined` as it's logical that it's either here or non-existant.

- string is used to hold an arbitrary information (such as a description o r a text). In this case, it is better to hold a sane default value for the field (it could be `""` or something else depending on the context).

One way to make is this is to leverage the `default` value and populate the field with it every time it becomes empty. If we want to represent it by `undefined` we just have to leave `default` blank. However, if we want to keep a default value at all times we provide it to `default` and we'll be assured that we will still be getting a good default in any case.

The behavior I'd like to see is the following: 
* when no `default` field is set and field is empty, output `undefined`
* when a `default` field is set and field is empty, output `default` value
mplis: I agree that there are two different, valid use cases for an empty string field. I've come across both since I started using this library. I like giving the user some control to define the behavior. 

`default` does feel like a natural place for this, though I worry that that would be overloading the field. We'd be making the assumption that the value you want to populate the field with before the user interacts with it (the current use of `default`) is *always* the same value you want in the field when the user clears it, which I don't think is a safe assumption.
Reggino: I agree with @quentin-sommer the current string-array handling feels weird and needs some attention :-)

I agree with @mplis it would simply be annoying whenever you would try to clear an input and it is instantly reset to some default value. 

Can't we just add new option to the uischema? Something like `ui:allowEmptyString`?
whollacsek: I also agree with @mplis, how about something like `ui:emptyValue`, which when not set means `undefined`, so the developer can explicitly set it to `""` (empty string) or `0` (zero). This should keep the current behavior as is, and won't break anything from the issues @n1k0 is referring to.
quentin-sommer: I've implemented the change on the pull request so you can try it out here 
https://quentin-sommer.github.io/react-jsonschema-form

the `firstName` field in the `simple` example as an `ui:emptyValue` of `""` 
and the `listOfStrings` field of the `array` example as an `ui:emptyValue` of `""`
n1k0: This is really good, and it solves all the constraints I had in mind. Could you please add some documentation in the README so we can land this patch?

Patch:
diff --git a/README.md b/README.md
index 0d8665cfb..7f6ce4f80 100644
--- a/README.md
+++ b/README.md
@@ -1294,10 +1294,12 @@ render((
 
 ### The case of empty strings
 
-When a text input is empty, the field in form data is set to `undefined`. String fields that use `enum` and a `select` widget work similarly and will have an empty option at the top of the options list that when selected will result in the field being `undefined`.
+When a text input is empty, the field in form data is set to `undefined`. String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `undefined`.
 
 One consequence of this is that if you have an empty string in your `enum` array, selecting that option in the `select` input will cause the field to be set to `undefined`, not an empty string.
 
+If you want to have the field set to a default value when empty you can provide a `ui:emptyValue` field in the `uiSchema` object.
+
 ## Styling your forms
 
 This library renders form fields and widgets leveraging the [Bootstrap](http://getbootstrap.com/) semantics. That means your forms will be beautiful by default if you're loading its stylesheet in your page.
diff --git a/playground/samples/arrays.js b/playground/samples/arrays.js
index 9027237de..c4142a3eb 100644
--- a/playground/samples/arrays.js
+++ b/playground/samples/arrays.js
@@ -117,6 +117,9 @@ module.exports = {
     },
   },
   uiSchema: {
+    listOfStrings: {
+      items: { "ui:emptyValue": "" },
+    },
     multipleChoicesList: {
       "ui:widget": "checkboxes",
     },
diff --git a/playground/samples/simple.js b/playground/samples/simple.js
index 78ed99593..78cbd9a78 100644
--- a/playground/samples/simple.js
+++ b/playground/samples/simple.js
@@ -31,6 +31,7 @@ module.exports = {
   uiSchema: {
     firstName: {
       "ui:autofocus": true,
+      "ui:emptyValue": "",
     },
     age: {
       "ui:widget": "updown",
diff --git a/src/components/widgets/BaseInput.js b/src/components/widgets/BaseInput.js
index 819d90b85..2d5d0693b 100644
--- a/src/components/widgets/BaseInput.js
+++ b/src/components/widgets/BaseInput.js
@@ -16,7 +16,7 @@ function BaseInput(props) {
     ...inputProps
   } = props;
   const _onChange = ({ target: { value } }) => {
-    return props.onChange(value === "" ? undefined : value);
+    return props.onChange(value === "" ? options.emptyValue : value);
   };
   return (
     <input
diff --git a/src/components/widgets/TextareaWidget.js b/src/components/widgets/TextareaWidget.js
index 3ecee57af..bbee35fdc 100644
--- a/src/components/widgets/TextareaWidget.js
+++ b/src/components/widgets/TextareaWidget.js
@@ -14,7 +14,7 @@ function TextareaWidget(props) {
     onBlur,
   } = props;
   const _onChange = ({ target: { value } }) => {
-    return onChange(value === "" ? undefined : value);
+    return onChange(value === "" ? options.emptyValue : value);
   };
   return (
     <textarea


Test patch:
diff --git a/test/StringField_test.js b/test/StringField_test.js
index 3384c864a..6f69526dd 100644
--- a/test/StringField_test.js
+++ b/test/StringField_test.js
@@ -113,6 +113,20 @@ describe("StringField", () => {
       expect(comp.state.formData).eql(undefined);
     });
 
+    it("should handle an empty string change event with custom ui:defaultValue", () => {
+      const { comp, node } = createFormComponent({
+        schema: { type: "string" },
+        uiSchema: { "ui:emptyValue": "default" },
+        formData: "x",
+      });
+
+      Simulate.change(node.querySelector("input"), {
+        target: { value: "" },
+      });
+
+      expect(comp.state.formData).eql("default");
+    });
+
     it("should fill field with data", () => {
       const { node } = createFormComponent({
         schema: {
@@ -325,6 +339,20 @@ describe("StringField", () => {
       expect(comp.state.formData).eql(undefined);
     });
 
+    it("should handle an empty string change event with custom ui:defaultValue", () => {
+      const { comp, node } = createFormComponent({
+        schema: { type: "string" },
+        uiSchema: { "ui:widget": "textarea", "ui:emptyValue": "default" },
+        formData: "x",
+      });
+
+      Simulate.change(node.querySelector("textarea"), {
+        target: { value: "" },
+      });
+
+      expect(comp.state.formData).eql("default");
+    });
+
     it("should render a textarea field with rows", () => {
       const { node } = createFormComponent({
         schema: { type: "string" },