From 23fa1261eaab828ff4692b9ab44831bb7c17375b Mon Sep 17 00:00:00 2001 From: Lukas Boll Date: Mon, 9 Oct 2023 13:48:22 +0200 Subject: [PATCH 1/3] add dynamic enum Tutorial Co-authored-by: Stefan Dirix closes #270 --- content/docs/tutorial/dynamic-enum.mdx | 268 ++++++++++++++++++ docusaurus.config.js | 4 + src/components/common/api.js | 92 ++++++ .../common/country/CountryControl.js | 52 ++++ .../common/country/countryControlTester.js | 6 + src/components/common/region/RegionControl.js | 53 ++++ .../common/region/regionControlTester.js | 6 + src/components/docs/tutorials/dynamic-enum.js | 66 +++++ src/sidebars/docs.js | 2 +- 9 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 content/docs/tutorial/dynamic-enum.mdx create mode 100644 src/components/common/api.js create mode 100644 src/components/common/country/CountryControl.js create mode 100644 src/components/common/country/countryControlTester.js create mode 100644 src/components/common/region/RegionControl.js create mode 100644 src/components/common/region/regionControlTester.js create mode 100644 src/components/docs/tutorials/dynamic-enum.js diff --git a/content/docs/tutorial/dynamic-enum.mdx b/content/docs/tutorial/dynamic-enum.mdx new file mode 100644 index 00000000..a343754f --- /dev/null +++ b/content/docs/tutorial/dynamic-enum.mdx @@ -0,0 +1,268 @@ +--- +id: dynamic-enum +title: Dynamic Renderers +description: This tutorial describes how to create a dynamic enum +--- + +import { WithRegionRenderer } from '../../../src/components/docs/tutorials/dynamic-enum'; + + +In this tutorial, you will learn how to handle dynamic data in React using [custom renderers](./custom-renderers), React Context, and the `useJsonForms` hook. +This approach allows you to build flexible and interactive forms that adapt to user selections and API responses. + +### Scenario + +Imagine a form where users need to provide their location by selecting a country, a region and a city. +The options for countries and regions are fetched from an API. +The available regions depend on the selected country. +To address those requirements, we'll create custom renderers for country and region. + + + + +#### Schema + +To begin, let's introduce the corresponding JSON schema. +We have created an object with properties for country, region, and city. +In our example, the schema also includes a property `x-url`, which specifies the entry point of the corresponding API. +Both `country` and `region` have a property `x-endpoint`, indicating the endpoint from which the data should be fetched. +Additionally, they have a field specifying which fields depend on the input. +In the case of the `country` field, the `region` and `city` fields depend on it and will get reset, if the value of the `country` changes. +The `city` field, in turn, is dependent on the `region` field. + +```js +{ + "type": "object", + "x-url": "www.api.com", + "properties": { + "country": { + "type": "string", + "x-endpoint": "countries", + "dependencies": ["region", "city"] + }, + "region": { + "type": "string", + "x-endpoint": "regions", + "dependencies": ["city"] + }, + "city": { + "type": "string" + } + } +} +``` + + +### Accessing Schema Data and Initialising the React Context + +In this step we will access the data from the schema and initialize the react context. + +#### Accessing the API URL from Schema + +To access the URL defined from the schema we can simply access the `x-url` attribute. + +```js +const url = schema['x-url']; +``` + +#### Initializing the React Context + +Now that we have access to the API URL, we can use React Context to make this data available across our renderers. +[React Context](https://react.dev/learn/passing-data-deeply-with-context) allows you to share data deep in the component tree to access data without needing to pass additional properties through the component hierarchy. +To set up the React Context for your API service, create it in your application as follows: + +```js +export const APIContext = React.createContext(new API(url)); + +const App = () =>{ + + ... + +} +``` + +#### Accessing the API context + + +Access the API service using the context: + +```js +const api = React.useContext(APIContext); +``` + +Changing the context's value will trigger a re-render of components that use it. + + +### The Country Renderer + +The core of the country renderer is a dropdown, therefore we can reuse the MaterialEnumControl from the React Material renderer set. +To reuse material renderers, the Unwrapped renderers must be used. (more information regarding reusing renderers can be seen [here](./custom-renderers#reusing-existing-controls)) + +```js +import { Unwrapped, WithOptionLabel } from '@jsonforms/material-renderers'; + +const { MaterialEnumControl } = Unwrapped; + +... + + +... +``` + +With the `MaterialEnumControl`in place the main question remains how to set the `options` and the `handleChange` attribute. +To determine the available options, we need to access the API. +And to implement the `handleChange` function, we need access to the `dependent` field in the schema. + +#### Accessing Schema Data + +The `endpoint` and `dependent` fields can be obtained from the schema object provided to the custom renderer via JSON Forms. +Since these fields are not part of the standard JSON schema type in JSON Forms, we must add them to the schema's interface and access them as follows: + +```js +type JsonSchemaWithDependenciesAndEndpoint = JsonSchema & { + dependent: string[]; + endpoint: string; +}; +const CountryControl = ( + props: ControlProps & OwnPropsOfEnum & WithOptionLabel & TranslateProps +) => { +... + + const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; + const endpoint = schema.endpoint; + const dependent = schema.dependent +... +} +``` + +#### Country Renderer Implementation + +The country renderer uses the `APIContext` to query the API and fetch the available options. +We utilize the `useEffect` hook to initialize the options. +While waiting for the API response, we set the available options to empty and display a loading spinner. +In the `handleChange` function, we set the new selected value and reset all dependent fields; +When changing the country, both the region and city will be reset to `undefined`. + +```js +import { Unwrapped, WithOptionLabel } from '@jsonforms/material-renderers'; + +const { MaterialEnumControl } = Unwrapped; + +type JsonSchemaWithDependenciesAndEndpoint = JsonSchema & { + dependent: string[]; + endpoint: string; +}; + +const CountryControl = ( + props: ControlProps & OwnPropsOfEnum & WithOptionLabel & TranslateProps +) => { + const { handleChange } = props; + const [options, setOptions] = useState([]); + const api = React.useContext(APIContext); + const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; + + const endpoint = schema.endpoint; + const dependent: string[] = schema.dependent ? schema.dependent : []; + + useEffect(() => { + api.get(endpoint).then((result) => { + setOptions(result); + }); + }, []); + + if (options.length === 0) { + return ; + } + + return ( + { + handleChange(path, value); + dependent.forEach((path) => { + handleChange(path, undefined); + }); + }} + options={options.map((option) => { + return { label: option, value: option }; + })} + /> + ); +}; + +export default withJsonFormsEnumProps( + withTranslateProps(React.memo(CountryControl)), + false +); +``` + +Now all that´s left to do is to [create a tester](./custom-renderers#2-create-a-tester) and [register](./custom-renderers#3-register-the-renderer) the new custom renderer in our application. + +### The Region Renderer + +The region renderer can be implemented similarly to the country renderer. +It also accesses the API via the context and includes `endpoint` and `dependent` fields defined in its schema. +However, the options, on the other hand, are also dependent on the selected country. +JSON Forms provides the `useJsonForms` hook, allowing you to access form data and trigger component rerenders when the data changes. +Let's use this hook in our region renderer to access the selected country: + +```js +import { Unwrapped, WithOptionLabel } from '@jsonforms/material-renderers'; +const { MaterialEnumControl } = Unwrapped; + +type JsonSchemaWithDependenciesAndEndpont = JsonSchema & { + dependent: string[]; + endpoint: string; +}; + +const RegionControl = ( + props: ControlProps & OwnPropsOfEnum & WithOptionLabel & TranslateProps +) => { + const schema = props.schema as JsonSchemaWithDependenciesAndEndpont; + const { handleChange } = props; + const [options, setOptions] = useState([]); + const api = React.useContext(APIContext); + const country = useJsonForms().core?.data.country; + const [previousCountry, setPreviousCountry] = useState(); + + const endpoint = schema.endpoint; + const dependent: string[] = schema.dependent ? schema.dependent : []; + + if (previousCountry !== country) { + setOptions([]); + setPreviousCountry(country); + api.get(endpoint + '/' + country).then((result) => { + setOptions(result); + }); + } + + if (options.length === 0 && country !== undefined) { + return ; + } + + return ( + { + handleChange(path, value); + dependent.forEach((path) => { + handleChange(path, undefined); + }); + }} + options={options.map((option) => { + return { label: option, value: option }; + })} + /> + ); +}; + +export default withJsonFormsEnumProps( + withTranslateProps(React.memo(RegionControl)), + false +); +``` +Again we need to create a [create a tester](./custom-renderers#2-create-a-tester) and [register](./custom-renderers#3-register-the-renderer) the new custom renderer. \ No newline at end of file diff --git a/docusaurus.config.js b/docusaurus.config.js index 3e3a0256..1b45a340 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -222,6 +222,10 @@ module.exports = { to: '/docs/tutorial/custom-renderers', from: '/docs/custom-renderers', }, + { + to: '/docs/tutorial/dynamic-enum', + from: '/docs/dynamic-enum', + }, { to: '/docs/tutorial/multiple-forms', from: '/docs/multiple-forms', diff --git a/src/components/common/api.js b/src/components/common/api.js new file mode 100644 index 00000000..05667214 --- /dev/null +++ b/src/components/common/api.js @@ -0,0 +1,92 @@ +export class API { + url; + + constructor(url) { + this.url = url; + } + + async get(endpoint){ + switch (this.url + '/' + endpoint) { + case 'www.api.com/regions/Germany': + return germanStates; + case 'www.api.com/regions/US': + return usStates; + case 'www.api.com/countries': + return ['Germany', 'US']; + default: + return []; + } + } +} + +const germanStates = [ + 'Berlin', + 'Bayern', + 'Niedersachsen', + 'Baden-Württemberg', + 'Rheinland-Pfalz', + 'Sachsen', + 'Thüringen', + 'Hessen', + 'Nordrhein-Westfalen', + 'Sachsen-Anhalt', + 'Brandenburg', + 'Mecklenburg-Vorpommern', + 'Hamburg', + 'Schleswig-Holstein', + 'Saarland', + 'Bremen', +]; + +const usStates = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components/common/country/CountryControl.js b/src/components/common/country/CountryControl.js new file mode 100644 index 00000000..0eb87d7f --- /dev/null +++ b/src/components/common/country/CountryControl.js @@ -0,0 +1,52 @@ +import React, { useEffect } from 'react'; +import { useState } from 'react'; +import { withJsonFormsEnumProps, withTranslateProps } from '@jsonforms/react'; +import { CircularProgress } from '@mui/material'; +import { Unwrapped } from '@jsonforms/material-renderers'; +import { APIContext } from '../../docs/tutorials/dynamic-enum'; + +const { MaterialEnumControl } = Unwrapped; + + +const CountryControl = ( + props +) => { + const { handleChange } = props; + const [options, setOptions] = useState([]); + const api = React.useContext(APIContext); + const schema = props.schema ; + + const endponit = schema.endpoint; + const dependent = schema.dependent ? schema.dependent : []; + + useEffect(() => { + setOptions([]); + api.get(endponit).then((result) => { + setOptions(result); + }); + }, [api, endponit]); + + if (options.length === 0) { + return ; + } + + return ( + { + handleChange(path, value); + dependent.forEach((path) => { + handleChange(path, undefined); + }); + }} + options={options.map((option) => { + return { label: option, value: option }; + })} + /> + ); +}; + +export default withJsonFormsEnumProps( + withTranslateProps(React.memo(CountryControl)), + false +); \ No newline at end of file diff --git a/src/components/common/country/countryControlTester.js b/src/components/common/country/countryControlTester.js new file mode 100644 index 00000000..a6a7d559 --- /dev/null +++ b/src/components/common/country/countryControlTester.js @@ -0,0 +1,6 @@ +import { rankWith, scopeEndsWith } from '@jsonforms/core'; + +export default rankWith( + 3, //increase rank as needed + scopeEndsWith('country') +); diff --git a/src/components/common/region/RegionControl.js b/src/components/common/region/RegionControl.js new file mode 100644 index 00000000..6c5ce02b --- /dev/null +++ b/src/components/common/region/RegionControl.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { useState } from 'react'; +import { useJsonForms, withJsonFormsEnumProps, withTranslateProps } from '@jsonforms/react'; +import { CircularProgress } from '@mui/material'; +import { Unwrapped } from '@jsonforms/material-renderers'; +import { APIContext } from '../../docs/tutorials/dynamic-enum'; +const { MaterialEnumControl } = Unwrapped; + +const RegionControl = ( + props +) => { + const schema = props.schema; + const { handleChange } = props; + const [options, setOptions] = useState([]); + const api = React.useContext(APIContext); + const country = useJsonForms().core?.data.country; + const [previousCountry, setPreviousCountry] = useState(); + + const endponit = schema.endpoint; + const dependent = schema.dependent ? schema.dependent : []; + + if (previousCountry !== country) { + setOptions([]); + setPreviousCountry(country); + api.get(endponit + '/' + country).then((result) => { + setOptions(result); + }); + } + + if (options.length === 0 && country !== undefined) { + return ; + } + + return ( + { + handleChange(path, value); + dependent.forEach((path) => { + handleChange(path, undefined); + }); + }} + options={options.map((option) => { + return { label: option, value: option }; + })} + /> + ); +}; + +export default withJsonFormsEnumProps( + withTranslateProps(React.memo(RegionControl)), + false +); \ No newline at end of file diff --git a/src/components/common/region/regionControlTester.js b/src/components/common/region/regionControlTester.js new file mode 100644 index 00000000..d81ed49c --- /dev/null +++ b/src/components/common/region/regionControlTester.js @@ -0,0 +1,6 @@ +import { rankWith, scopeEndsWith } from '@jsonforms/core'; + +export default rankWith( + 3, //increase rank as needed + scopeEndsWith('region') +); diff --git a/src/components/docs/tutorials/dynamic-enum.js b/src/components/docs/tutorials/dynamic-enum.js new file mode 100644 index 00000000..5017cd11 --- /dev/null +++ b/src/components/docs/tutorials/dynamic-enum.js @@ -0,0 +1,66 @@ +import { Demo } from '../../common/Demo'; +import { materialRenderers } from '@jsonforms/material-renderers'; +import React from 'react'; + +import countryControlTester from '../../common/country/countryControlTester'; +import CountryControl from '../../common/country/CountryControl'; +import regionControlTester from '../../common/region/regionControlTester'; +import RegionControl from '../../common/region/RegionControl'; +import { API } from '../../common/api'; + +const data = { +}; + +const schema = { + "x-url": "www.api.com", + "type": "object", + "properties": { + "country": { + "type": "string", + "endpoint": "countries", + "dependent": ["region", "city"] + }, + "region": { + "type": "string", + "endpoint": "regions", + "dependent": ["city"] + }, + "city": { + "type": "string" + }, + }} + + +const regionUiSchema = { + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/country" + }, + { + "type": "Control", + "scope": "#/properties/region" + }, + { + "type": "Control", + "scope": "#/properties/city" + } + ] + } + +export const WithRegionRenderer = () => ( + +); + +const url = schema['x-url']; +export const APIContext = React.createContext(new API(url)); \ No newline at end of file diff --git a/src/sidebars/docs.js b/src/sidebars/docs.js index fe7fd18b..7762b03a 100644 --- a/src/sidebars/docs.js +++ b/src/sidebars/docs.js @@ -22,7 +22,7 @@ module.exports = { type: 'category', label: 'Tutorials', collapsed: false, - items: ['tutorial/create-app', 'tutorial/custom-layouts', 'tutorial/custom-renderers', 'tutorial/multiple-forms'], + items: ['tutorial/create-app', 'tutorial/custom-layouts', 'tutorial/custom-renderers', 'tutorial/dynamic-enum', 'tutorial/multiple-forms'], }, 'api', { From 1ee172924855608d4b8ea52bf8bce93d2091db10 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Fri, 10 Jan 2025 13:23:08 +0100 Subject: [PATCH 2/3] clean up code examples --- content/docs/tutorial/dynamic-enum.mdx | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/content/docs/tutorial/dynamic-enum.mdx b/content/docs/tutorial/dynamic-enum.mdx index a343754f..d56354e6 100644 --- a/content/docs/tutorial/dynamic-enum.mdx +++ b/content/docs/tutorial/dynamic-enum.mdx @@ -38,12 +38,12 @@ The `city` field, in turn, is dependent on the `region` field. "country": { "type": "string", "x-endpoint": "countries", - "dependencies": ["region", "city"] + "x-dependencies": ["region", "city"] }, "region": { "type": "string", "x-endpoint": "regions", - "dependencies": ["city"] + "x-dependencies": ["city"] }, "city": { "type": "string" @@ -55,7 +55,7 @@ The `city` field, in turn, is dependent on the `region` field. ### Accessing Schema Data and Initialising the React Context -In this step we will access the data from the schema and initialize the react context. +In this step we will access the data from the schema and initialize the React context. #### Accessing the API URL from Schema @@ -115,11 +115,11 @@ const { MaterialEnumControl } = Unwrapped; With the `MaterialEnumControl`in place the main question remains how to set the `options` and the `handleChange` attribute. To determine the available options, we need to access the API. -And to implement the `handleChange` function, we need access to the `dependent` field in the schema. +And to implement the `handleChange` function, we need access to the `x-dependencies` field in the schema. #### Accessing Schema Data -The `endpoint` and `dependent` fields can be obtained from the schema object provided to the custom renderer via JSON Forms. +The `x-endpoint` and `x-dependencies` fields can be obtained from the schema object provided to the custom renderer via JSON Forms. Since these fields are not part of the standard JSON schema type in JSON Forms, we must add them to the schema's interface and access them as follows: ```js @@ -133,8 +133,8 @@ const CountryControl = ( ... const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; - const endpoint = schema.endpoint; - const dependent = schema.dependent + const endpoint = schema['x-endpoint']; + const dependent = schema['x-dependencies']; ... } ``` @@ -163,10 +163,10 @@ const CountryControl = ( const { handleChange } = props; const [options, setOptions] = useState([]); const api = React.useContext(APIContext); - const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; + const schema = props.schema as JsonSchemaDependenciesAndEndpoint; - const endpoint = schema.endpoint; - const dependent: string[] = schema.dependent ? schema.dependent : []; + const endpoint = schema['x-endpoint']; + const dependent: string[] = schema['x-dependencies'] ? schema['x-dependencies'] : []; useEffect(() => { api.get(endpoint).then((result) => { @@ -205,7 +205,7 @@ Now all that´s left to do is to [create a tester](./custom-renderers#2-create-a ### The Region Renderer The region renderer can be implemented similarly to the country renderer. -It also accesses the API via the context and includes `endpoint` and `dependent` fields defined in its schema. +It also accesses the API via the context and includes `x-endpoint` and `x-dependencies` fields defined in its schema. However, the options, on the other hand, are also dependent on the selected country. JSON Forms provides the `useJsonForms` hook, allowing you to access form data and trigger component rerenders when the data changes. Let's use this hook in our region renderer to access the selected country: @@ -214,7 +214,7 @@ Let's use this hook in our region renderer to access the selected country: import { Unwrapped, WithOptionLabel } from '@jsonforms/material-renderers'; const { MaterialEnumControl } = Unwrapped; -type JsonSchemaWithDependenciesAndEndpont = JsonSchema & { +type JsonSchemaWitDependenciesAndEndpont = JsonSchema & { dependent: string[]; endpoint: string; }; @@ -222,15 +222,15 @@ type JsonSchemaWithDependenciesAndEndpont = JsonSchema & { const RegionControl = ( props: ControlProps & OwnPropsOfEnum & WithOptionLabel & TranslateProps ) => { - const schema = props.schema as JsonSchemaWithDependenciesAndEndpont; + const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; const { handleChange } = props; const [options, setOptions] = useState([]); const api = React.useContext(APIContext); const country = useJsonForms().core?.data.country; const [previousCountry, setPreviousCountry] = useState(); - const endpoint = schema.endpoint; - const dependent: string[] = schema.dependent ? schema.dependent : []; + const endpoint = schema['x-endpoint']; + const dependent: string[] = schema['x-dependencies'] ? schema['x-dependencies'] : []; if (previousCountry !== country) { setOptions([]); From f5db824576080cb744231bfdd88fae13af5ecb7a Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Fri, 10 Jan 2025 13:24:27 +0100 Subject: [PATCH 3/3] replace x-dependencies with x-dependents --- content/docs/tutorial/dynamic-enum.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content/docs/tutorial/dynamic-enum.mdx b/content/docs/tutorial/dynamic-enum.mdx index d56354e6..4ebb2e6c 100644 --- a/content/docs/tutorial/dynamic-enum.mdx +++ b/content/docs/tutorial/dynamic-enum.mdx @@ -38,12 +38,12 @@ The `city` field, in turn, is dependent on the `region` field. "country": { "type": "string", "x-endpoint": "countries", - "x-dependencies": ["region", "city"] + "x-dependents": ["region", "city"] }, "region": { "type": "string", "x-endpoint": "regions", - "x-dependencies": ["city"] + "x-dependents": ["city"] }, "city": { "type": "string" @@ -115,11 +115,11 @@ const { MaterialEnumControl } = Unwrapped; With the `MaterialEnumControl`in place the main question remains how to set the `options` and the `handleChange` attribute. To determine the available options, we need to access the API. -And to implement the `handleChange` function, we need access to the `x-dependencies` field in the schema. +And to implement the `handleChange` function, we need access to the `x-dependents` field in the schema. #### Accessing Schema Data -The `x-endpoint` and `x-dependencies` fields can be obtained from the schema object provided to the custom renderer via JSON Forms. +The `x-endpoint` and `x-dependents` fields can be obtained from the schema object provided to the custom renderer via JSON Forms. Since these fields are not part of the standard JSON schema type in JSON Forms, we must add them to the schema's interface and access them as follows: ```js @@ -134,7 +134,7 @@ const CountryControl = ( const schema = props.schema as JsonSchemaWithDependenciesAndEndpoint; const endpoint = schema['x-endpoint']; - const dependent = schema['x-dependencies']; + const dependent = schema['x-dependents']; ... } ``` @@ -166,7 +166,7 @@ const CountryControl = ( const schema = props.schema as JsonSchemaDependenciesAndEndpoint; const endpoint = schema['x-endpoint']; - const dependent: string[] = schema['x-dependencies'] ? schema['x-dependencies'] : []; + const dependent: string[] = schema['x-dependents'] ? schema['x-dependents'] : []; useEffect(() => { api.get(endpoint).then((result) => { @@ -205,7 +205,7 @@ Now all that´s left to do is to [create a tester](./custom-renderers#2-create-a ### The Region Renderer The region renderer can be implemented similarly to the country renderer. -It also accesses the API via the context and includes `x-endpoint` and `x-dependencies` fields defined in its schema. +It also accesses the API via the context and includes `x-endpoint` and `x-dependents` fields defined in its schema. However, the options, on the other hand, are also dependent on the selected country. JSON Forms provides the `useJsonForms` hook, allowing you to access form data and trigger component rerenders when the data changes. Let's use this hook in our region renderer to access the selected country: @@ -230,7 +230,7 @@ const RegionControl = ( const [previousCountry, setPreviousCountry] = useState(); const endpoint = schema['x-endpoint']; - const dependent: string[] = schema['x-dependencies'] ? schema['x-dependencies'] : []; + const dependent: string[] = schema['x-dependents'] ? schema['x-dependents'] : []; if (previousCountry !== country) { setOptions([]);