# Async dynamic enum for autocomplete text field

**URL:** <https://jsonforms.discourse.group/t/async-dynamic-enum-for-autocomplete-text-field/2787>\
**Category:** General\
**Created:** [October 15, 2025, 3:26pm UTC](https://jsonforms.discourse.group/t/async-dynamic-enum-for-autocomplete-text-field/2787 "2025-10-15T15:26:19Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![perki](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/perki/32/1326_2.png) [@perki](https://jsonforms.discourse.group/u/perki)\
**Post date:** [October 15, 2025, 3:26pm UTC](https://jsonforms.discourse.group/t/async-dynamic-enum-for-autocomplete-text-field/2787/1 "2025-10-15T15:26:19Z")

</div>

While looking for a way to populate an `enum` or `oneOf` list for a text by fetching options.

I got a working solution with a custom renderer but using `@mui/material`

I would be pleased to have some hints about how I could use `@jsonform/*` Components instead;

There is a working demo on github: [GitHub - perki/jsonforms-autocomplete-async-test: JsonForms AutoComplete Async Test](https://github.com/perki/jsonforms-autocomplete-async-test)

**App.tsx** see full content [here](https://github.com/perki/jsonforms-autocomplete-async-test/blob/main/src/App.tsx)

The Renderer takes an async callback options `autoCompleteAsyncCallBack` to fetch the data:

```javascript
const uischema = {
   type: 'Control',
   scope: '#/properties/api_field',
   options: {
     autoCompleteAsyncCallBack,
     autoCompleteHelperText // optional, render a text when a choice is made
   }
};

```

**AutoCompleteAsync.tsx**

```javascript
// AutoCompleteAsyncControl.js
import { useState, useEffect } from 'react';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { Autocomplete , FormControl, TextField } from '@mui/material';

// no direct API import here; options are provided via uischema callback

interface AutoCompleteAsyncProps {
  data: any;
  handleChange(path: string, value: any): void;
  path: string;
  schema: JsonSchema;
  uischema: UISchemaElement;
}

export type AutoCompleteOption = {
  data: any,
  const: string,
  title: string
}

/**
 * @return a textual description of the selected item if any
 */
export type AutoCompleteAsyncCallBack = (query: string) => Promise<AutoCompleteOption[]>;

/**
 * @return a textual description of the selected item if any
 */
export type AutoCompleteHelperText = (option: AutoCompleteOption) => string;

const AutoCompleteAsyncControl = ({ data, handleChange, path, schema, uischema }: AutoCompleteAsyncProps) => {
  // -- load from usischema config
  const autoCompleteAsyncCallBack: AutoCompleteAsyncCallBack = uischema?.options?.autoCompleteAsyncCallBack;
  if (autoCompleteAsyncCallBack == null) throw new Error('Missing uischema.options.autoCompleteAsyncCallBack');
  const autoCompleteHelperText: AutoCompleteHelperText = uischema?.options?.autoCompleteHelperText || (() => schema.description);
  // --
  const [options, setOptions] = useState<AutoCompleteOption[]>([]);
  const [loading, setLoading] = useState(true);
  const [inputValue, setInputValue] = useState( data?.title || '');

  // API call on input change
  useEffect(() => {
    const fetchOptions = async () => {
      setLoading(true);
      try {
        const newOptions = await autoCompleteAsyncCallBack(inputValue);
        if (Array.isArray(newOptions)) {
          setOptions(newOptions);
        } else {
          setOptions([]);
        }
      } catch (error) {
        setOptions([]);
        console.error('Failed to fetch options:', error);
      } finally {
        setLoading(false);
      }
    };
    fetchOptions();
  }, [inputValue]);

  const myOnChange = function (event: any, newValue: AutoCompleteOption | null) {
    handleChange(path, newValue);
  };

  const newInputValue = function (event: any, newInputValue: string) {
    if (event == null) return; // avoid change on init
    setInputValue(newInputValue);
  };

  return (
    <FormControl fullWidth>
      <Autocomplete
        options={options}
        loading={loading}
        forcePopupIcon={false}
        onChange={myOnChange}
        inputValue={inputValue}
        onInputChange={newInputValue}
        getOptionLabel={(option) => option?.title ?? ''}
        isOptionEqualToValue={(option, value) => option?.const === value?.const}
        renderOption={(liProps, option) => (
          <li {...liProps} key={option.const}>{option.title}</li>
        )}
        renderInput={(params) => (
          <TextField
            {...params}
            label={schema.title || path}
            helperText={autoCompleteHelperText(data)}
          />
        )}
      />
    </FormControl>
  );
};

// --- Tester
import { rankWith, hasOption, isStringControl, and, UISchemaElement, JsonSchema } from '@jsonforms/core';

// This tester will trigger the custom renderer when a UI schema control has
// a specific custom property, like "render: 'autocomplete-api'".
export const AutocompleteAsyncTester = rankWith(
  3, // A higher rank gives this renderer precedence
  and(
    isStringControl,
    hasOption('autoCompleteAsyncCallBack')
  )
);

const AutoCompleteAsyncRenderer = withJsonFormsControlProps(AutoCompleteAsyncControl);

export default AutoCompleteAsyncRenderer;

```

---

<div class="post-metadata">

**Author:** ![lucas-koehler](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/lucas-koehler/32/336_2.png) [@lucas-koehler](https://jsonforms.discourse.group/u/lucas-koehler)\
**Post date:** [October 21, 2025, 9:30am UTC](https://jsonforms.discourse.group/t/async-dynamic-enum-for-autocomplete-text-field/2787/2 "2025-10-21T09:30:50Z")

</div>

Hi @perki ,

You have a look at the docs regarding re-using existing controls: [Custom Renderers - JSON Forms](https://jsonforms.io/docs/tutorial/custom-renderers/#reusing-existing-controls)

In any case you have to do the async fetching within the custom part of your code. You can look into reusing the [MaterialEnumControl](https://github.com/eclipsesource/jsonforms/blob/master/packages/material-renderers/src/controls/MaterialEnumControl.tsx) . To use it within your custom renderer’s code, use the unwrapped variant:

```typescript
import { Unwrapped } from ‘@jsonforms/material-renderers’;
const { MaterialEnumControl } = Unwrapped;

```

---

<div class="post-metadata">

**Author:** ![perki](https://yyz2.discourse-cdn.com/free1/user_avatar/jsonforms.discourse.group/perki/32/1326_2.png) [@perki](https://jsonforms.discourse.group/u/perki)\
**Post date:** [November 27, 2025, 9:37am UTC](https://jsonforms.discourse.group/t/async-dynamic-enum-for-autocomplete-text-field/2787/3 "2025-11-27T09:37:02Z")

</div>

Thanks @lucas-koehler ,

Yes `MaterialEnumControl` is the component I want to customize.

I have already tried following the custom rendering guide - without success.

I’ll have a deeper look into it.
