# How to update another control?

**URL:** <https://jsonforms.discourse.group/t/how-to-update-another-control/1661>\
**Category:** General\
**Created:** [August 14, 2023, 1:24pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661 "2023-08-14T13:24:57Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![richkroll](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/richkroll/32/512_2.png) [@richkroll](https://jsonforms.discourse.group/u/richkroll)\
**Post date:** [August 14, 2023, 1:24pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/1 "2023-08-14T13:24:57Z")

</div>

I am working to create dependent select controls (e.g. state/city, select state, get list of cities). In this example, when the state is changed, I want to clear any selections made on the city control. I have created an onChange handler that uses handleChange to clear the values. When invoked on the primary control, the JSONForms state of the dependent control is properly updated but the control itself continues to display the previously selected values. I have also tried to use `dispatch(Actions.update(controlName, () => []))` without success.

In the below example I have two controls, a ‘primary\_control’ and a ‘dependent\_control’. When the primary is modified, I want to clear the selections of the dependent.

What is the proper way to properly update the other control?

```auto
import {
    JsonSchema,
    ControlProps,
    RankedTester,
    rankWith,
    schemaMatches,
    and,
    isEnumControl,
    OwnPropsOfEnum,
    EnumOption,
    Actions,
} from '@jsonforms/core';
import { JsonFormsStateContext, withJsonFormsContext, withJsonFormsEnumProps } from '@jsonforms/react';
import { Autocomplete, Grid, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
type JsonSchemaWithDependency = JsonSchema & { use_dependent_control: boolean };

interface WithContext {
    ctx: JsonFormsStateContext,
    props: ControlProps & OwnPropsOfEnum
}

export const SimpleControl = (props: WithContext) => {
    const schema = props.props.schema as JsonSchemaWithDependency;
    const coreOptions = props.props.options ? props.props.options : []
    const [options, setOptions] = useState<EnumOption[]>(coreOptions);

    const {
        id,
        label,
        handleChange,
        path,
    } = props.props;

    const {
        dispatch
    } = props.ctx

    useEffect(() => {
        // "dynamically" set the options of this control e.g. from an API.
        setOptions([{ "label": "Foo", "value": "1" }, { "label": "Bar", "value": "2" }])
    }, []);

    const onChange = (_ev: any, newValues: any) => {
        // update this control
        handleChange(path, newValues);

        if ('primary_control' === path) {
            // This updates the JSONForms state but the MUI control is not updated
            handleChange('dependent_control', []);

            // This also updated the JSONForms state but leaves the control unchanged.
            // if (dispatch) {
            // dispatch(Actions.update('dependent_control', () => []))
            // }
        }
    }

    return (
        <Grid container>
            <Grid item xs={12}>
                <Autocomplete
                    id={id}
                    autoComplete
                    fullWidth
                    disableCloseOnSelect
                    freeSolo={false}
                    isOptionEqualToValue={(option, value) => option.value === value.value}
                    getOptionLabel={(option) => option.value}
                    options={options ? options : []}
                    onChange={onChange}
                    multiple={true}
                    renderInput={(params) => (
                        <TextField {...params} label={label} variant="outlined" />
                    )}
                />
            </Grid>
        </Grid>
    );
};

export const simpleControlTester: RankedTester = rankWith(
    5,
    and(
        isEnumControl,
        schemaMatches((schema) => schema.hasOwnProperty('use_dependent_control')),
    )
);

export default withJsonFormsEnumProps(withJsonFormsContext(SimpleControl));

```

```auto
{
  "type": "object",
  "properties": {
    "primary_control": {
      "title": "Primary Control",
      "type": "string",
      "enum": ["East", "West", "North", "South"],
      "use_dependent_control": true
    },
    "dependent_control": {
      "title": "Dependent Control",
      "type": "string",
      "enum": ["Never", "Daily", "Weekly", "Monthly"],
      "use_dependent_control": true
    }
  }
}

```

```auto
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/primary_control"
    },
    {
      "type": "Control",
      "scope": "#/properties/dependent_control"
    }
  ]
}

```

---

<div class="post-metadata">

**Author:** ![richkroll](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/richkroll/32/512_2.png) [@richkroll](https://jsonforms.discourse.group/u/richkroll)\
**Post date:** [August 16, 2023, 2:11pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/2 "2023-08-16T14:11:20Z")

</div>

I was able to simplify my prior code to pass all provided props to a MaterialEnumControl and am able to have the expected dependency work as expected (clear the data & UI component). However, in this simplified version, I have an issue with applying custom options to the control(s). In the below example, when I load the options within useEffect(), the UI is populated correctly but validation fails when any value is selected. I tried dispatching setSchema, update, and updateCore but could not determine the correct way to update JSONForms.

I seem to be missing the proper way to update the schema and to dynamically provide options to the control. Any help would be GREATLY appreciated!

```auto
export const SimpleControl2 = (props: ControlProps & TranslateProps & OwnPropsOfEnum & JsonFormsStateContext) => {
    const [options, setOptions] = useState<EnumOption[]>(props.props.options);

    const {
        handleChange,
        path,
    } = props.props;

    const {
        dispatch
    } = props.ctx

    useEffect(() => {
        const enumValues: EnumOption[] = [{ "label": "Foo", "value": "1" }, { "label": "Bar", "value": "2" }, { "label": "East", "value": "East" }, { "label": "Never", "value": "Never" }]

        // "dynamically" set the options of this control e.g. from an API.
        // this populates the control, but the validation fails for all selections.
        setOptions(enumValues)
    }, []);

    const onChange = (_ev: any, newValues: any) => {
        // update this control
        handleChange(path, newValues)

        if ('primary_control' === path) {
            // This now updates the 'dependent_control' data & UI as expected
            handleChange('dependent_control', undefined);
        }
    }

    return (
        <Grid container>
            <Grid item xs={12}>
                <MaterialEnumControl {...props.props} options={options} handleChange={onChange} description='hello there!' />
            </Grid>
        </Grid>
    );
};

export const simpleControlTester2: RankedTester = rankWith(
    5,
    and(
        isEnumControl,
        schemaMatches((schema) => schema.hasOwnProperty('use_dependent_control2')),
    )
);

```

---

<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 16, 2023, 6:57pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/3 "2023-08-16T18:57:39Z")

</div>

Hi @richkroll,

> [@richkroll](#):
>
> I tried dispatching setSchema, update, and updateCore but could not determine the correct way to update JSONForms.

The `setSchema` is no longer used internally in JSON Forms and is only there for legacy reasons. If you want to dynamically update the `schema` then you have to hand over an updated `schema` to the `JsonForms` component.

* * *

However I would advise against dynamically updating the JSON Schema for dynamic enums. You already manage the options manually, so there really is no need to rely on the JSON Schema for the enum entries. Also there is no validation benefit as you manage the enum entries anyway.

Often dynamic enums are just modeled as `type: 'string'` without specifying any option. Instead usually some kind of endpoint discovery is added there. If you want to encode initial options to already render before your endpoint returns, then you can add them via a custom attribute so that they are not picked up by AJV.

The `SimpleControl2` should be wrapped with `withJsonFormsControlProps` and use the unwrapped variant of the `MaterialEnumControl`, i.e.

```auto
import { Unwrapped } from '@jsonforms/material-renderers';
const { MaterialEnumControl } = Unwrapped;

```

---

<div class="post-metadata">

**Author:** ![richkroll](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/richkroll/32/512_2.png) [@richkroll](https://jsonforms.discourse.group/u/richkroll)\
**Post date:** [August 17, 2023, 12:00pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/4 "2023-08-17T12:00:05Z")

</div>

Thanks @sdirix! Changing to a string (or array of strings) was the key. I made the faulty assumption that AJV also drove the rules, which we require in our implementation, so I was focused on modifying the underlying schema so AJV would work as expected.

In the hopes that it can help someone else, below is the final version of my demo which includes dynamically populating the select list (e.g. from an API).

schema.json

```auto
{
  "type": "object",
  "properties": {
    "primary_control": {
      "title": "Primary Control",
      "type": "string",
      "use_dependent_control": true
    },
    "dependent_control": {
      "title": "Dependent Control",
      "type": "string",
      "use_dependent_control": true
    }
  }
}

```

uischema.json

```auto
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/primary_control"
        },
        {
          "type": "Control",
          "scope": "#/properties/dependent_control",
          "rule": {
            "effect": "SHOW",
            "condition": {
              "scope": "#",
              "schema": {
                "properties": {
                  "const": true
                },
                "required": ["primary_control"]
              }
            }
          }
        }
      ]
    }
  ]
}

```

SimpleControl.tsx

```auto
import {
    ControlProps,
    RankedTester,
    rankWith,
    schemaMatches,
    and,
    OwnPropsOfEnum,
    EnumOption,
} from '@jsonforms/core';
import { Unwrapped } from '@jsonforms/material-renderers';
import { TranslateProps, withJsonFormsControlProps, withTranslateProps } from '@jsonforms/react';
import { Grid } from '@mui/material';
import { useEffect, useState } from 'react';

const { MaterialEnumControl } = Unwrapped;

export const SimpleControl = (props: ControlProps & TranslateProps & OwnPropsOfEnum) => {
    const [options, setOptions] = useState<EnumOption[]>([]);

    const {
        handleChange,
        path,
    } = props;

    useEffect(() => {
        // "dynamically" load the set of options for this control e.g. from an API.
        const enumValues: EnumOption[] = [{ "label": "Foo", "value": "1" }, { "label": "Bar", "value": "2" }, { "label": "East", "value": "East" }, { "label": "Never", "value": "Never" }]
        setOptions(enumValues)
    }, []);

    const onChange = (_ev: any, newValues: any) => {
        // update this control
        handleChange(path, newValues)

        // The dependent path needs to be defined in the schema and dynamically resolved via core data in the context.
        if ('primary_control' === path) {
            // clear the dependent control if the primary control changes
            handleChange('dependent_control', undefined);
        }
    }

    return (
        <Grid container>
            <Grid item xs={12}>
                <MaterialEnumControl {...props} options={options} handleChange={onChange} />
            </Grid>
        </Grid>
    );
};

export const simpleControlTester: RankedTester = rankWith(
    5,
    and(
        schemaMatches((schema) => schema.hasOwnProperty('use_dependent_control')),
    )
);

export default withJsonFormsControlProps(
    withTranslateProps(SimpleControl),
    false
);

```

---

<div class="post-metadata">

**Author:** ![richkroll](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/richkroll/32/512_2.png) [@richkroll](https://jsonforms.discourse.group/u/richkroll)\
**Post date:** [August 17, 2023, 2:33pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/5 "2023-08-17T14:33:21Z")

</div>

@sdirix In further testing this approach, I discovered that the call to `handleChange('dependent_control',undefined)` does not trigger the controls change handler. Is there a recommended way to trigger that a changed occurred?

What I am looking to achieve is to have the dependent control be aware that it needs to reload its data. An example would be a state control and a dependent city control. When the state control changes, I would like to clear the city control and load the cites based on the current value of the state control. I know that I can use ctx.core.data to look up the current value of the state control, but without the change handler being triggered, I am at a loss for how to trigger the query for the dependent data.

---

<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 18, 2023, 9:16am UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/6 "2023-08-18T09:16:55Z")

</div>

Hi @richkroll,

> [@richkroll](#):
>
> `handleChange('dependent_control',undefined)` does not trigger the controls change handler.

Correct, all this does is to update the `data` of `dependent_control` to `undefined`. In case that the `data` was not already `undefined` before, the `dependent_control` will rerender with `props.data` being `undefined`.

> [@richkroll](#):
>
> What I am looking to achieve is to have the dependent control be aware that it needs to reload its data.

The implementation design here depends on your exact requirements and what to optimize for. A typical design looks like this:

- Each control which relies on external API gets a corresponding API injected. This can be done by creating a corresponding context outside of JSON Forms and consuming it in your custom renderers
- Each control which requires data from other fields resolves that data from the form-wide data store via `Resolve.data(formWideData, pathToData)`. The `formWideData` can be accessed via the `useJsonForms()` hook, while the `pathToData` is typically specified via the JSON Schema
  - Because the data is resolved, the control will automatically rerender when one of the required upon data changes. Therefore the control knows when its API requests are outdated and can perform new ones, either immediately or once needed, e.g. when the user clicks on the drop down.

- A control on which other controls depends, often has to delete or set a new value for all dependent data. This should all be handled directly in this control, just like you already implemented.
  - When a new value has to be set then often a new API request is needed. The same API request has to be performed in the dependent controls itself to resolve the options. In case optimization is needed here, the API needs to be stateful and just return immediately on second invocation or the result of past queries are also stored in the React context

---

<div class="post-metadata">

**Author:** ![richkroll](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/richkroll/32/512_2.png) [@richkroll](https://jsonforms.discourse.group/u/richkroll)\
**Post date:** [August 24, 2023, 8:23pm UTC](https://jsonforms.discourse.group/t/how-to-update-another-control/1661/7 "2023-08-24T20:23:53Z")

</div>

Thanks for the pointers in the right direction!
