Repository: mozilla-services/react-jsonschema-form
Pull request #28 (2016-01-09 09:42:48)

Custom schema fields
## Custom SchemaField

**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care.

You can provide your own implementation of the `SchemaField` base React component for rendering any JSONSchema field type, including objects and arrays. This is useful when you want to augment a given field type with supplementary powers.

To proceed so, you can pass a `SchemaField` prop to the `Form` component instance; here's a rather silly example wrapping the standard `SchemaField` lib component:

```jsx
import SchemaField from "react-jsonschema-form/lib/components/fields/SchemaField";

const CustomSchemaField = function(props) {
  return (
    <div id="custom">
      <p>Yeah, I'm pretty dumb.</p>
      <SchemaField {...props} />
    </div>
  );
};

render((
  <Form schema={schema}
        uiSchema={uiSchema}
        formData={formData}
        SchemaField={CustomSchemaField} />
), document.getElementById("app"));
```

If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field.


Patch:
diff --git a/README.md b/README.md
index 2e4c65284..f348979bd 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A default, very basic CSS stylesheet is provided, though you're encouraged to bu
 
 ## Usage
 
-```js
+```jsx
 import React, { Component } from "react";
 import { render } from "react-dom";
 
@@ -79,7 +79,7 @@ JSONSchema is limited for describing how a given data type should be rendered as
 
 Example:
 
-```js
+```jsx
 const uiSchema =  {
   done: {
     widget: "radio" // could also be "select"
@@ -119,7 +119,7 @@ Here's a list of supported alternative widgets for different JSONSchema data typ
 
 The UISchema object accepts a `classNames` property for each field of the schema:
 
-```js
+```jsx
 const uiSchema = {
   title: {
     classNames: "task-title foo-bar"
@@ -148,7 +148,7 @@ You can provide your own custom widgets to a uiSchema for the following json dat
 - `boolean`
 - `date-time`
 
-```js
+```jsx
 const schema = {
   type: "string"
 };
@@ -179,6 +179,36 @@ The following props are passed to the widget component:
 - `placeholder`: The placeholder value, if any;
 - `options`: The list of options for `enum` fields;
 
+## Custom SchemaField
+
+**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care.
+
+You can provide your own implementation of the `SchemaField` base React component for rendering any JSONSchema field type, including objects and arrays. This is useful when you want to augment a given field type with supplementary powers.
+
+To proceed so, you can pass a `SchemaField` prop to the `Form` component instance; here's a rather silly example wrapping the standard `SchemaField` lib component:
+
+```jsx
+import SchemaField from "react-jsonschema-form/lib/components/fields/SchemaField";
+
+const CustomSchemaField = function(props) {
+  return (
+    <div id="custom">
+      <p>Yeah, I'm pretty dumb.</p>
+      <SchemaField {...props} />
+    </div>
+  );
+};
+
+render((
+  <Form schema={schema}
+        uiSchema={uiSchema}
+        formData={formData}
+        SchemaField={CustomSchemaField} />
+), document.getElementById("app"));
+```
+
+If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field.
+
 ## Development server
 
 ```
diff --git a/src/components/Form.js b/src/components/Form.js
index 429876970..0df9cac4d 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -1,11 +1,9 @@
 import React, { Component, PropTypes } from "react";
 import { Validator } from "jsonschema";
-
-import { getDefaultFormState } from "../utils";
 import SchemaField from "./fields/SchemaField";
+import { getDefaultFormState } from "../utils";
 import ErrorList from "./ErrorList";
 
-
 export default class Form extends Component {
   static defaultProps = {
     uiSchema: {}
@@ -78,14 +76,16 @@ export default class Form extends Component {
   render() {
     const {schema, uiSchema} = this.props;
     const {formData} = this.state;
+    const _SchemaField = this.props.SchemaField || SchemaField;
     return (
       <form className="rjsf" onSubmit={this.onSubmit.bind(this)}>
         {this.renderErrors()}
-        <SchemaField
+        <_SchemaField
           schema={schema}
           uiSchema={uiSchema}
           formData={formData}
-          onChange={this.onChange.bind(this)} />
+          onChange={this.onChange.bind(this)}
+          SchemaField={_SchemaField}/>
         <p><button type="submit">Submit</button></p>
       </form>
     );
@@ -100,6 +100,7 @@ if (process.env.NODE_ENV !== "production") {
     onChange: PropTypes.func,
     onError: PropTypes.func,
     onSubmit: PropTypes.func,
+    SchemaField: PropTypes.func,
   };
 }
 
diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js
index 9b795b8ea..67b02ca07 100644
--- a/src/components/fields/ArrayField.js
+++ b/src/components/fields/ArrayField.js
@@ -1,7 +1,6 @@
 import React, { Component, PropTypes } from "react";
 
 import { getDefaultFormState } from "../../utils";
-import SchemaField from "./SchemaField";
 
 
 class ArrayField extends Component {
@@ -65,6 +64,7 @@ class ArrayField extends Component {
     const {schema, uiSchema, name} = this.props;
     const title = schema.title || name;
     const {items} = this.state;
+    const SchemaField = this.props.SchemaField;
     return (
       <fieldset
         className={`field field-array field-array-of-${schema.items.type}`}>
@@ -80,7 +80,8 @@ class ArrayField extends Component {
                   uiSchema={uiSchema.items}
                   formData={items[index]}
                   required={this.isItemRequired(schema.items)}
-                  onChange={this.onChange.bind(this, index)} />
+                  onChange={this.onChange.bind(this, index)}
+                  SchemaField={SchemaField}/>
                 <p className="array-item-remove">
                   <button type="button"
                     onClick={this.onDropClick.bind(this, index)}>-</button></p>
@@ -102,6 +103,7 @@ if (process.env.NODE_ENV !== "production") {
     uiSchema: PropTypes.object,
     onChange: PropTypes.func.isRequired,
     formData: PropTypes.array,
+    SchemaField: PropTypes.func.isRequired,
   };
 }
 
diff --git a/src/components/fields/BooleanField.js b/src/components/fields/BooleanField.js
index 74062e55c..8a20ed729 100644
--- a/src/components/fields/BooleanField.js
+++ b/src/components/fields/BooleanField.js
@@ -1,7 +1,7 @@
 import React, { PropTypes } from "react";
 
 import { defaultFieldValue, getAlternativeWidget } from "../../utils";
-import CheckboxField from "./../widgets/CheckboxWidget";
+import CheckboxWidget from "./../widgets/CheckboxWidget";
 
 function BooleanField({schema, name, uiSchema, formData, required, onChange}) {
   const {title, description} = schema;
@@ -19,7 +19,7 @@ function BooleanField({schema, name, uiSchema, formData, required, onChange}) {
     const Widget = getAlternativeWidget(schema.type, widget);
     return <Widget options={[true, false]} {... commonProps} />;
   }
-  return <CheckboxField {...commonProps} />;
+  return <CheckboxWidget {...commonProps} />;
 }
 
 if (process.env.NODE_ENV !== "production") {
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js
index 355889066..1db56f55b 100644
--- a/src/components/fields/ObjectField.js
+++ b/src/components/fields/ObjectField.js
@@ -1,7 +1,6 @@
 import React, { Component, PropTypes } from "react";
 
 import { getDefaultFormState } from "../../utils";
-import SchemaField from "./SchemaField";
 
 
 class ObjectField extends Component {
@@ -40,6 +39,7 @@ class ObjectField extends Component {
   render() {
     const {schema, uiSchema, name} = this.props;
     const title = name || schema.title;
+    const SchemaField = this.props.SchemaField;
     return (
       <fieldset>
         {title ? <legend>{title}</legend> : null}
@@ -54,7 +54,8 @@ class ObjectField extends Component {
               schema={schema.properties[name]}
               uiSchema={uiSchema[name]}
               formData={this.state[name]}
-              onChange={this.onChange.bind(this, name)} />
+              onChange={this.onChange.bind(this, name)}
+              SchemaField={SchemaField}/>
             );
         })
       }</fieldset>
@@ -69,6 +70,7 @@ if (process.env.NODE_ENV !== "production") {
     onChange: PropTypes.func.isRequired,
     formData: PropTypes.object,
     required: PropTypes.bool,
+    SchemaField: PropTypes.func.isRequired,
   };
 }
 


Test patch:
diff --git a/test/index_test.js b/test/index_test.js
index 10e48c18d..85f3c6fa3 100644
--- a/test/index_test.js
+++ b/test/index_test.js
@@ -5,6 +5,7 @@ import sinon from "sinon";
 import React from "react";
 import { Simulate, renderIntoDocument } from "react-addons-test-utils";
 import { findDOMNode } from "react-dom";
+import SchemaField from "../src/components/fields/SchemaField";
 
 import Form from "../src";
 
@@ -44,6 +45,22 @@ describe("Form", () => {
     });
   });
 
+  describe("Custom SchemaField", () => {
+    const CustomSchemaField = function(props) {
+      return (<div id="custom"><SchemaField {...props} /></div>);
+    };
+
+    it("should use the specified custom SchemaType property", () => {
+      const {node} = createComponent({
+        schema: {type: "string"},
+        SchemaField: CustomSchemaField
+      });
+
+      expect(node.querySelectorAll("#custom > .field input[type=text]"))
+        .to.have.length.of(1);
+    });
+  });
+
   describe("StringField", () => {
     describe("TextWidget", () => {
       it("should render a string field", () => {