Repository: mozilla-services/react-jsonschema-form
Pull request #826 (2018-01-29 17:39:58)

pass raw errors to  field widgets
### Reasons for making this change

See issue #727 

The current implementation of ArrayField, BooleanField, and StringField don't pass the error schema down to the widgets they render. This makes rendering errors as part of a custom control tricky. 

This PR solves this by passing the raw errors down, but maintaining the default implementation for displaying errors with the libraries built in widgets.

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've checked the rendering of the Markdown text I've added
  - [ ] If I'm adding a new section, I've updated the Table of Content
* [ 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**
  - [ ] I've updated the playground with an example use of the feature


llamamoray: Thinking about this more, I don't think the ArrayField or ObjectField even need to be passed the raw errors. The objective of the PR is to pass field level errors down to the fields so that custom field components can render errors in any way they wish. I think I can remove the props for ArrayField  and ObjectField.
llamamoray: @glasserc any feedback on my latest changes?

Patch:
diff --git a/src/components/fields/BooleanField.js b/src/components/fields/BooleanField.js
index 553148628..8f10dccff 100644
--- a/src/components/fields/BooleanField.js
+++ b/src/components/fields/BooleanField.js
@@ -21,6 +21,7 @@ function BooleanField(props) {
     readonly,
     autofocus,
     onChange,
+    rawErrors,
   } = props;
   const { title } = schema;
   const { widgets, formContext } = registry;
@@ -44,6 +45,7 @@ function BooleanField(props) {
       registry={registry}
       formContext={formContext}
       autofocus={autofocus}
+      rawErrors={rawErrors}
     />
   );
 }
@@ -67,6 +69,7 @@ if (process.env.NODE_ENV !== "production") {
       definitions: PropTypes.object.isRequired,
       formContext: PropTypes.object.isRequired,
     }),
+    rawErrors: PropTypes.arrayOf(PropTypes.string),
   };
 }
 
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js
index e66299e1c..105c7f325 100644
--- a/src/components/fields/SchemaField.js
+++ b/src/components/fields/SchemaField.js
@@ -206,6 +206,7 @@ function SchemaFieldRender(props) {
       autofocus={autofocus}
       errorSchema={fieldErrorSchema}
       formContext={formContext}
+      rawErrors={__errors}
     />
   );
 
diff --git a/src/components/fields/StringField.js b/src/components/fields/StringField.js
index 9ecee6eb9..c2887e28e 100644
--- a/src/components/fields/StringField.js
+++ b/src/components/fields/StringField.js
@@ -24,6 +24,7 @@ function StringField(props) {
     onBlur,
     onFocus,
     registry = getDefaultRegistry(),
+    rawErrors,
   } = props;
   const { title, format } = schema;
   const { widgets, formContext } = registry;
@@ -51,6 +52,7 @@ function StringField(props) {
       autofocus={autofocus}
       registry={registry}
       placeholder={placeholder}
+      rawErrors={rawErrors}
     />
   );
 }
@@ -77,6 +79,7 @@ if (process.env.NODE_ENV !== "production") {
     disabled: PropTypes.bool,
     readonly: PropTypes.bool,
     autofocus: PropTypes.bool,
+    rawErrors: PropTypes.arrayOf(PropTypes.string),
   };
 }
 
diff --git a/src/components/widgets/BaseInput.js b/src/components/widgets/BaseInput.js
index 108b250d1..9a39e2967 100644
--- a/src/components/widgets/BaseInput.js
+++ b/src/components/widgets/BaseInput.js
@@ -22,6 +22,9 @@ function BaseInput(props) {
   const _onChange = ({ target: { value } }) => {
     return props.onChange(value === "" ? options.emptyValue : value);
   };
+
+  const { rawErrors, ...cleanProps } = inputProps;
+
   return (
     <input
       className="form-control"
@@ -29,7 +32,7 @@ function BaseInput(props) {
       disabled={disabled}
       autoFocus={autofocus}
       value={value == null ? "" : value}
-      {...inputProps}
+      {...cleanProps}
       onChange={_onChange}
       onBlur={onBlur && (event => onBlur(inputProps.id, event.target.value))}
       onFocus={onFocus && (event => onFocus(inputProps.id, event.target.value))}


Test patch:
diff --git a/test/SchemaField_test.js b/test/SchemaField_test.js
index 3e94841b8..6376b4fd5 100644
--- a/test/SchemaField_test.js
+++ b/test/SchemaField_test.js
@@ -321,5 +321,25 @@ describe("SchemaField", () => {
       expect(matches).to.have.length.of(1);
       expect(matches[0].textContent).to.contain("test");
     });
+
+    describe("Custom error rendering", () => {
+      const customStringWidget = props => {
+        return <div className="custom-text-widget">{props.rawErrors}</div>;
+      };
+
+      it("should pass rawErrors down to custom widgets", () => {
+        const { node } = createFormComponent({
+          schema,
+          uiSchema,
+          validate,
+          widgets: { BaseInput: customStringWidget },
+        });
+        submit(node);
+
+        const matches = node.querySelectorAll(".custom-text-widget");
+        expect(matches).to.have.length.of(1);
+        expect(matches[0].textContent).to.eql("test");
+      });
+    });
   });
 });