# anyOf elements type doesn't update

**URL:** <https://jsonforms.discourse.group/t/anyof-elements-type-doesnt-update/1649>\
**Category:** General\
**Created:** [August 8, 2023, 8:10pm UTC](https://jsonforms.discourse.group/t/anyof-elements-type-doesnt-update/1649 "2023-08-08T20:10:22Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![elenajdanova](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/elenajdanova/32/768_2.png) [@elenajdanova](https://jsonforms.discourse.group/u/elenajdanova)\
**Post date:** [August 8, 2023, 8:10pm UTC](https://jsonforms.discourse.group/t/anyof-elements-type-doesnt-update/1649/1 "2023-08-08T20:10:22Z")

</div>

here’s my schema

```auto
export const Schema = {
  "title": "SQLDatasource",
  "description": "--Public API--",
  "type": "object",
  "properties": {
    "type": {
      "title": "Type",
      "default": "sql",
      "enum": ["sql"],
      "type": "string"
    },
    "name": {
      "title": "Name",
      "type": "string"
    },
    "assets": {
      "title": "Assets",
      "default": [],
      "type": "array",
      "items": {
        "anyOf": [
          {
            "$ref": "#/definitions/TableAsset"
          },
          {
            "$ref": "#/definitions/QueryAsset"
          }
        ]
      }
    },
  },
  "required": ["name"],
  "additionalProperties": false,
  "definitions": {
    "TableAsset": {
      "title": "TableAsset",
      "description": "A _SQLAsset Mixin\n\nThis is used as a mixin for _SQLAsset subclasses to give them the TableAsset functionality\nthat can be used by different SQL datasource subclasses.\n\nFor example see TableAsset defined in this module and SqliteTableAsset defined in\nsqlite_datasource.py",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "type": {
          "title": "Type",
          "default": "table",
          "enum": ["table"],
          "type": "string"
        },
        "table_name": {
          "title": "Table Name",
          "description": "Name of the SQL table. Will default to the value of `name` if not provided.",
          "default": "",
          "type": "string"
        },
        "schema_name": {
          "title": "Schema Name",
          "type": "string"
        }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "QueryAsset": {
      "title": "QueryAsset",
      "description": "Base model for most fluent datasource related pydantic models.\n\nAdds yaml dumping and parsing methods.\n\nExtra fields are not allowed.\n\nSerialization methods default to `exclude_unset = True` to prevent serializing\nconfigs full of mostly unset default values.\nAlso prevents passing along unset kwargs to BatchSpec.\nhttps://docs.pydantic.dev/usage/exporting_models/",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "type": {
          "title": "Type",
          "default": "query",
          "enum": ["query"],
          "type": "string"
        },
        "query": {
          "title": "Query",
          "type": "string"
        }
      },
      "required": ["name", "query"],
      "additionalProperties": false
    }
  }
} as const

```

and here’s my UISchenas for tableAsset

```auto
import { JsonFormsUISchemaRegistryEntry, JsonSchema, RuleEffect } from "@jsonforms/core"
import { UISchema } from "./ui-schema"

export const tableAssetUISchema: UISchema = {
  type: "VerticalLayout",
  elements: [
    {
      type: "Control",
      scope: "#/properties/type",
      rule: {
        effect: RuleEffect.HIDE,
        condition: {},
      },
    },
    {
      type: "Control",
      scope: "#/properties/name",
      label: "Asset name",
    },
    {
      type: "Control",
      scope: "#/properties/table_name",
    },
  ],
}

```

The problem is that inside `data` whatever option I select (table asset or query asset) it always sets type to Table

here’s my custom anyOfRenderer

```auto
import { useCallback, useState } from "react"
import { Radio, RadioChangeEvent } from "antd"
import {
  CombinatorSubSchemaRenderInfo,
  createCombinatorRenderInfos,
  isAnyOfControl,
  JsonFormsRendererRegistryEntry,
  JsonSchema,
  rankWith,
  StatePropsOfCombinator,
} from "@jsonforms/core"
import { JsonFormsDispatch, withJsonFormsAnyOfProps } from "@jsonforms/react"
import styled from "styled-components"

import { CombinatorProperties } from "./CombinatorProperties"
import { startCase } from "lodash-es"

export const AnyOfRendererRegistryEntry: JsonFormsRendererRegistryEntry = {
  tester: rankWith(3, isAnyOfControl),
  renderer: withJsonFormsAnyOfProps(AnyOfRenderer),
}

const StyledRadioGroup = styled(Radio.Group)`
  margin-bottom: ${({ theme }) => theme.spacing.vertical.s};
`

export function AnyOfRenderer({
  cells,
  indexOfFittingSchema,
  path,
  renderers,
  rootSchema,
  schema,
  uischema,
  uischemas,
  visible,
}: StatePropsOfCombinator) {
  const [selectedAnyOf, setSelectedAnyOf] = useState<number>(indexOfFittingSchema || 0)
  const handleChange = useCallback((e: RadioChangeEvent) => setSelectedAnyOf(e.target.value), [setSelectedAnyOf])
  const anyOf = "anyOf"
  const anyOfRenderInfos = createCombinatorRenderInfos(
    (schema as JsonSchema).anyOf as JsonSchema[],
    rootSchema,
    anyOf,
    uischema,
    path,
    uischemas,
  )

  const computeLabel = (anyOfRenderInfo: CombinatorSubSchemaRenderInfo) => {
    const label = anyOfRenderInfo.label.startsWith("anyOf") ? anyOfRenderInfo.schema.title : anyOfRenderInfo.label
    return startCase(label)
  }

  return (
    <div hidden={!visible}>
      <CombinatorProperties schema={schema} combinatorKeyword={anyOf} path={path} />
      <StyledRadioGroup
        options={anyOfRenderInfos.map((anyOfRenderInfo, index) => ({
          // revert this when this is merged & released: https://github.com/eclipsesource/jsonforms/pull/2165#issuecomment-1640981617
          label: computeLabel(anyOfRenderInfo),
          value: index,
        }))}
        onChange={handleChange}
        value={selectedAnyOf}
      />

      {anyOfRenderInfos.map(
        (anyOfRenderInfo, anyOfIndex) =>
          selectedAnyOf === anyOfIndex && (
            <JsonFormsDispatch
              key={anyOfIndex}
              schema={anyOfRenderInfo.schema}
              uischemas={uischemas}
              uischema={anyOfRenderInfo.uischema}
              path={path}
              renderers={renderers}
              cells={cells}
            />
          ),
      )}
    </div>
  )
}

```

Another issue is that in case I type in “Table fields” some data then switch to Query type and use that fields I get data from all mixed fields and I have no Idea what type (query or table) was selected in UI

As I see in Material renderer and in mine (as it was mostly copied) change of Tab \ Radio happens outside `<JsonFormsDispatch />` component. So is there a way to tell `<JsonFormsDispatch />` what tab is selected right now?

---

<div class="post-metadata">

**Author:** ![sdirix](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/sdirix/32/1073_2.png) [@sdirix](https://jsonforms.discourse.group/u/sdirix)\
**Post date:** [August 8, 2023, 11:00pm UTC](https://jsonforms.discourse.group/t/anyof-elements-type-doesnt-update/1649/2 "2023-08-08T23:00:18Z")

</div>

Hi @elenajdanova,

The problem with the combinators, i.e. `anyOf`, `allOf`, `oneOf` is that they are hard to support generically. Our combinator renderers are therefore very limited. In the case of `anyOf` and `oneOf` they try to determine the fitting subschema by running a limited validation run against each of the sub schemas. However this often fails, for example if the entry is currently not valid against any of them. Not only that but the validation step is also pretty expensive, so if you can skip that then that’s much better.

> [@elenajdanova](#):
>
> The problem is that inside `data` whatever option I select (table asset or query asset) it always sets type to Table

If I see correctly then you probably handle the default value via AJV? Sadly AJV does not support `default` for combinators (i.e. `anyOf`, `allOf` and `oneOf`) simply because it’s often not decidable which default value to take.

I think the reason why you always get `table` injected, is because of our subschema validation runs. We reuse the same AJV which is handed in. So if `default` generation is turned on, our intermediate validation runs will modify the data in place. This is an oversight on our part.

* * *

In most cases, including yours, I would recommend **not** using the generic combinator utilities already provided by JSON Forms but tailoring your custom renderer exactly to your needs.

In your case the `anyOf` entries seem to be uniquely identifiable by a `type` property which both of them model. So I would recommend implementing a custom `anyOf`/`oneOf` renderer which:

- knows that the `type` property is the one to look at for distinguishing between the different entries
- therefore handles the `type` property itself, for example by rendering it first. It collects all valid entries for `type` by walking through all `anyOf` entries. For the remaining properties it can dispatch back to JSON Forms.
  - an easy way to accomplish this is to just dispatch the rendering of the whole object to JSON Forms with a small custom type renderer which just renders nothing. Alternatively the `type` could also be set to `visible: false`.

- when the `type` is modifed by the user the data object is cleared, the type set accordingly and a dispatch with the correct schema back to JSON Forms executed

This will render a far better and more performant UI than just replicating the very generic JSON Forms anyof renderers.

Does this make sense to you? Let me know if you have questions for the implementation.

---

<div class="post-metadata">

**Author:** ![elenajdanova](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/elenajdanova/32/768_2.png) [@elenajdanova](https://jsonforms.discourse.group/u/elenajdanova)\
**Post date:** [August 10, 2023, 2:27am UTC](https://jsonforms.discourse.group/t/anyof-elements-type-doesnt-update/1649/4 "2023-08-10T02:27:10Z")

</div>

Follow up question, as I’m trying to put together custom anyOf renderer and set the data I want via  
`handleChange(path, { name: 't', query: 't', type: "query" })`

I still get out of JsonForms data that has empty fields from other type like  
`{name: "t", query: "t", table_name: "", type: "query"}`
