Dynamic and Computed Options

Dynamic Options

Sometimes you need select or radiogroup options which are dynamically loaded based on some other fields content. You can do that by defining dynamicOptions like this:

{
    id: "post",
    label: "Choose a post:",
    type: "select",
    options: posts,
    isRequired: true
},
{
    id: "comment",
    label: "Comment",
    type: "select",
    options: [{
        value: "", text: "Select a posts comment ..."
    }],
    dynamicOptions: {
        watchFields: ['post'],
        events: ["init", "change"],
        enableCaching: true,
        loader: async (data) => {
            if (!data || !data.post) {
                return [{
                    value: "",
                    text: "Select a posts comment ..."
                }];
            }
            const response = await axios.get(`...api/posts/${data.post}/comments`);
            return response.data.map(comment => {
                return {
                    value: comment.id, text: comment.name
                }
            });
        },
        onOptionsChange: (options, updatedData, handleChange) => {
            // do something with new options
        }
    },
    isRequired: true
}

The dynamicOptions propery in the fields config has following options, which are all required:

  • watchFields: An array of field ids which should be watched for changes
  • events: An array of events of the watched fields which should trigger the loader
  • loader: A function which returns the new options
    • data props: This contains the latest data of the form, normally used for the API call params
    • handleChange prop: Sometimes it's useful to set data of other fields with what the api call returned, use this callback to do that
  • enableCaching: Set this to true if you want loaded options to be cached to reduce api calls
  • onOptionsChange: Callback which will be called right after the loader received the new options

An example for a more complicated loader:

const loadCities = async (data, handleChange) => {

    // Return empty options if not all required values are set:

    if (
        !data || 
        !data.country || 
        !data.postalCode || 
        !isValidPostalCode(data.country, data.postalCode)
    ) return [];


    // Load all cities for this code:

    const cities = await api('cities', {
      country: data.country,
      postalCode: data.postalCode
    });


    // If there is only one city for this postalcode, directly
    // set the city (there can be multiple locations for one code).
    // We call handleChange so that the loader for the streets options
    // is automatically triggered:

    if (cities.length === 1) {
      data.city = cities[0];
      handleChange('city', data.city, undefined, data);
      setData(data);
    }

    return cities;
  };

Bonus: Stages automatically prevents race conditions for asynchronously loaded dynamic options!

Non Async Dynamic Options

For non async dynamic options, you can simply define a function which calculates them:

{
    id: "dynSelect",
    label: "Dynamic Select",
    type: "select",
    options: (path, data, allData) => {
        return [
            {   
                text: "Entry 1",
                value: "entry1"
            },
            {   
                text: "Entry 2",
                value: "entry2"
            }
        ];
    }
}

Computed Options

There is a second way to create dynamic options, designed mainly to use values from a collection inside a field which has options, for example a select. Here's an example for such a field config:

{
    id: "team",
    type: "select",
    label: "Team",
    computedOptions: {
        source: "teams",
        initWith: [
            { value: "", text: "Please select ..." }
        ],
        map: (item) => {
            return {
                value: item.nr,
                text: item.name
            };
        },
        sort: (a, b) => a.name > b.name ? 1 : b.name > a.name ? -1 : 0,
        filter: (item) => !!item.nr
    }
}

For this one to work, there has to be a collection with the id teams, and at least containing the fields nr and name. It than creates options based on what the user entered in that collection. Here's an explanation of all the options:

  • source: This is the path key of the source collection
  • initWith: Usually you want to init the options array with an empty option, you can do it here
  • map: This is a callback which maps the values of the collection to the usual option props value and text
  • sort: This optional callback can be used to sort the entries
  • filter: Another optional option to filter the entries, for example to remove collection entries which haven't been filled in by the user

One neat extra of this computed options i sthat if you set this field to isUnique, than all the already chosen options will be disabled in the generated options.