Skip to content

Dynamic Fields

Fields whose options are loaded from an external source and cascade changes to dependent fields via computedProps.

What It Demonstrates

  • computedProps assigning options from a reactive store into field.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

Company details

Options are requested from an external source.

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;
        }],
      },
    ],
  },
];

Released under the MIT License.