Repository: mozilla-services/react-jsonschema-form (js)

Issue:
Issue with nesting oneOf inside an array that is inside of an object. 
### Prerequisites

- [x] I have read the [documentation](https://react-jsonschema-form.readthedocs.io/);
- [x] In the case of a bug report, I understand that providing a [SSCCE](http://sscce.org/) example is tremendously useful to the maintainers.
- [x] Ideally, I'm providing a [sample JSFiddle](https://jsfiddle.net/n1k0/f2y3fq7L/6/) or a [shared playground link](https://mozilla-services.github.io/react-jsonschema-form/) demonstrating the issue.

### Description

I am attempting to use oneOf to create an array, which will be filled with objects. These objects will have a string along with an array. 
this is the expected formData or object I want to receive on the backend:
```
{
typeProp: 'verify',
verifications: [
  {
    service: 'myService',
    endpoints: [
      {
        Description: 'myService1',
        Id: ''
      },
      {
        Description: 'myService2',
        Id: ''
      },
      {
        Description: 'myService3',
        Id: ''
      }
    ]
  },
  {
    serviceName: 'yourService',
    endpoints: [
      {
        endpointDescription: 'yourService1',
        endpointId: ''
      },
      {
        endpointDescription: 'yourService2',
        endpointId: ''
      }
    ]
  }
]
}
```

I am trying to use a dropdown on my typeProp, which would then show a dropdown for the service, once a selection is made for the service, I need the endpoints to show and also be a dropdown. Basically, all 3 fields will need to be a oneOf dropdown. 

Now, oneOf seems to be the best solution for me. However, the oneOf functionality does not seem to be able to be nested. Is there a way around this? Does anyone have a solution to my problem, or is this a bug in the code that will need to be reviewed? Any help is greatly appreciated

I'm not sure how to add a playground, but here is the code I currently have. I have tried every thing imagine-able of where to put the next oneOf but cannot find a solution.

Here is my code: 
```
{
      title: 'New,
      description: 'Create',
      type: 'object',
      properties: {
        type: {
          type: 'string',
          title: 'Type',
          enum: [
            'verification',
            'subscription'
          ]
        }
      },
      dependencies: {
        type: {
          oneOf: [
            {
              properties: {
                type: {
                  enum: ['verification']
                },
                verification: {
                  type: 'array',
                  title: 'Verifications',
                  items: {
                    type: 'object',
                    properties: {
                      serviceName: {
                        type: 'string',
                        title: 'Service Name',
                        enum: ['myService']
                      },
                      endpoints: {
                        type: 'array',
                        title: 'End Points',
                        items: {
                          type: 'object',
                          properties: {
                            endpointDesc: {
                              type: 'string',
                              title: 'EP Name',
                              enum: ['MyServiceEP']
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          ]
        }
      }
    }
```

Pull request:
Correctly use $ref when matching options with oneOf
Fixes #1173

Additionally, if a matching option isn't found, preserve the currently
selected option

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>

### 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
* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [ ] 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
* [ ] **I'm adding a new feature**
  - [ ] I've updated the playground with an example use of the feature


Patch:
diff --git a/src/components/fields/MultiSchemaField.js b/src/components/fields/MultiSchemaField.js
index 6ecaa8276..44401dc06 100644
--- a/src/components/fields/MultiSchemaField.js
+++ b/src/components/fields/MultiSchemaField.js
@@ -1,7 +1,12 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import * as types from "../../types";
-import { getUiOptions, getWidget, guessType } from "../../utils";
+import {
+  getUiOptions,
+  getWidget,
+  guessType,
+  retrieveSchema,
+} from "../../utils";
 import { isValid } from "../../validate";
 
 class AnyOfField extends Component {
@@ -29,8 +34,16 @@ class AnyOfField extends Component {
   }
 
   getMatchingOption(formData, options) {
+    const { definitions } = this.props.registry;
     for (let i = 0; i < options.length; i++) {
-      const option = options[i];
+      // Assign the definitions to the option, otherwise the match can fail if
+      // the new option uses a $ref
+      const option = Object.assign(
+        {
+          definitions,
+        },
+        options[i]
+      );
 
       // If the schema describes an object then we need to add slightly more
       // strict matching to the schema, because unless the schema uses the
@@ -81,15 +94,20 @@ class AnyOfField extends Component {
       }
     }
 
-    // If the form data matches none of the options, use the first option
-    return 0;
+    // If the form data matches none of the options, use the currently selected
+    // option, assuming it's available; otherwise use the first option
+    return this && this.state ? this.state.selectedOption : 0;
   }
 
   onOptionChange = option => {
     const selectedOption = parseInt(option, 10);
-    const { formData, onChange, options } = this.props;
-
-    const newOption = options[selectedOption];
+    const { formData, onChange, options, registry } = this.props;
+    const { definitions } = registry;
+    const newOption = retrieveSchema(
+      options[selectedOption],
+      definitions,
+      formData
+    );
 
     // If the new option is of type object and the current data is an object,
     // discard properties added using the old option.


Test patch:
diff --git a/test/oneOf_test.js b/test/oneOf_test.js
index 98ba91ed6..3fa952faf 100644
--- a/test/oneOf_test.js
+++ b/test/oneOf_test.js
@@ -464,4 +464,66 @@ describe("oneOf", () => {
       expect(node.querySelectorAll("input#root_bar")).to.have.length.of(1);
     });
   });
+
+  describe("definitions", () => {
+    it("should handle the $ref keyword correctly", () => {
+      const schema = {
+        definitions: {
+          fieldEither: {
+            type: "object",
+            oneOf: [
+              {
+                type: "object",
+                properties: {
+                  value: {
+                    type: "string",
+                  },
+                },
+              },
+              {
+                type: "object",
+                properties: {
+                  value: {
+                    type: "array",
+                    items: {
+                      $ref: "#/definitions/fieldEither",
+                    },
+                  },
+                },
+              },
+            ],
+          },
+        },
+        type: "object",
+        properties: {
+          value: {
+            type: "array",
+            items: {
+              $ref: "#/definitions/fieldEither",
+            },
+          },
+        },
+      };
+
+      const { node } = createFormComponent({
+        schema,
+      });
+
+      expect(node.querySelector(".array-item-add button")).not.eql(null);
+
+      Simulate.click(node.querySelector(".array-item-add button"));
+
+      const $select = node.querySelector("select");
+      expect($select).not.eql(null);
+      Simulate.change($select, {
+        target: { value: $select.options[1].value },
+      });
+
+      // This works because the nested "add" button will now be the first to
+      // appear in the dom
+      Simulate.click(node.querySelector(".array-item-add button"));
+
+      expect($select.value).to.eql($select.options[1].value);
+    });
+  });
 });