Dynamic Fields
Fields whose options are loaded from an external source and cascade changes to dependent fields via computedProps.
What It Demonstrates
computedPropsassigning options from a reactive store intofield.options- Cascading selections: choosing an industry triggers a request for matching team size options
- Resetting a child value when its options change and the current value is no longer valid
- A context wrapper that simulates an external API call
Example
IsDirty: false
Touched: false
Valid: true
// form values:
{
"company": {}
}
computedProps
Each computedProps callback runs reactively whenever its dependencies change. The industry select populates its options and triggers a load for team size; the team size select resets its value if the newly loaded options no longer include it:
ts
computedProps: [(field, value) => {
field.options = props.optionStore.industry;
// Load the teamSize options, depending on the selected industry
requestOptions('teamSize', { industry: value.value as string });
}],
},
{
name: 'teamSize',
type: 'select',
fieldOptions: { label: 'Team size' },
minOccurs: 0,
computedProps: [(field, value) => {
field.options = props.optionStore.teamSize;
// Reset the value if it is not in the new options
if (props.optionStore.teamSize.every(x => x.key !== value.value))
value.value = undefined;
}],Option Loading
The context wrapper simulates an external API — replace loadOptions with a real fetch in production:
ts
/**
* Simulate an external API.
*/
function loadOptions(optionName: OptionName, params?: LoadOptionsParams) {
if (optionName === 'industry') {
optionStore.industry = [
{ key: 'saas', value: 'SaaS' },
{ key: 'retail', value: 'Retail' },
{ key: 'healthcare', value: 'Healthcare' },
{ key: 'logistics', value: 'Logistics' },
];
}
if (optionName === 'teamSize') {
const industry = params?.industry;
optionStore.teamSize = industry ? (teamSizeOptionsByIndustry[industry] ?? []) : [];
}
}Full Metadata
ts
const metadata: Metadata[] = [
{
name: 'company',
type: 'heading',
fieldOptions: { label: 'Company details' },
children: [
{
name: 'industry',
type: 'select',
fieldOptions: { label: 'Industry' },
description: 'Options are requested from an external source.',
computedProps: [(field, value) => {
field.options = props.optionStore.industry;
// Load the teamSize options, depending on the selected industry
requestOptions('teamSize', { industry: value.value as string });
}],
},
{
name: 'teamSize',
type: 'select',
fieldOptions: { label: 'Team size' },
minOccurs: 0,
computedProps: [(field, value) => {
field.options = props.optionStore.teamSize;
// Reset the value if it is not in the new options
if (props.optionStore.teamSize.every(x => x.key !== value.value))
value.value = undefined;
}],
},
],
},
];