Skip to main content

Remote Autocomplete

Try it live​

Open Remote Autocomplete in the Lab →

Local vs remote autocomplete​

Local Autocomplete receives options in its schema. A remote autocomplete has no local options: it declares an optionsSource and the consuming application provides its data through dataSourceExecutor.

The Lab uses DummyJSON users as a public live endpoint. It is only a demonstration of a real integration; a production application owns its backend, HTTP client, authentication, authorization, and caching.

optionsSource: {
sourceId: 'dummyjson-users',
minSearchLength: 2,
}

minSearchLength prevents a search until the user enters two characters. DYONA Form does not fetch data automatically.

Search and resolve​

options.search receives request.input.searchText. This example calls https://dummyjson.com/users/search with that text, limit=10, and the fields needed for the label.

options.resolve receives request.input.value. It calls https://dummyjson.com/users/<id> so an initialValues ID can display its label before any search is made.

Each request also supplies context.fieldName, context.values, and request.signal. Pass the signal to fetch so superseded searches can be aborted. Check response.ok before using the response.

DYONA Form shows localized loading and error states while these requests are pending or fail.

Secondary dropdown information​

DyonaDataSourceOption optionally accepts secondaryLabel:

{
value: user.id,
label: `${user.firstName} ${user.lastName}`,
secondaryLabel: user.email,
}

The dropdown can show Emily Johnson with [email protected] below it. After selection, the input shows only Emily Johnson; secondaryLabel is presentation only and value remains the user ID. It is generic for provider + CUIT, customer + document, user + email, or product + code.

Complete example​

import {useCallback, useMemo} from 'react';
import {
DyonaForm,
type DyonaDataSourceExecutor,
type DyonaDataSourceOption,
type DyonaFormSchema,
} from '@typhonsoftware/dyona-form';

type DummyUser = {
id: number;
firstName: string;
lastName: string;
email: string;
};

type DummyUsersSearchResponse = {users: DummyUser[]};

const initialValues = {customerId: 1};

function mapUserToOption(user: DummyUser): DyonaDataSourceOption {
return {
value: user.id,
label: `${user.firstName} ${user.lastName}`,
secondaryLabel: user.email,
};
}

export function RemoteCustomerForm() {
const schema = useMemo<DyonaFormSchema>(() => ({
title: 'Customer selection',
tabs: [{
id: 'customer',
title: 'Customer',
rows: [{columns: [{
name: 'customerId',
label: 'Customer',
type: 'autocomplete',
optionsSource: {sourceId: 'dummyjson-users', minSearchLength: 2},
}]}],
}],
}), []);

const dataSourceExecutor = useCallback<DyonaDataSourceExecutor>(async (request) => {
if (request.operation === 'options.search') {
const query = new URLSearchParams({
q: request.input.searchText,
limit: '10',
select: 'id,firstName,lastName,email',
});
const response = await fetch(
`https://dummyjson.com/users/search?${query}`,
{signal: request.signal},
);
if (!response.ok) {
throw new Error(`DummyJSON user search failed (HTTP ${response.status})`);
}
const data = await response.json() as DummyUsersSearchResponse;
return {operation: 'options.search', data: data.users.map(mapUserToOption)};
}

const response = await fetch(
`https://dummyjson.com/users/${request.input.value}`,
{signal: request.signal},
);
if (!response.ok) {
throw new Error(`DummyJSON user resolve failed (HTTP ${response.status})`);
}
const user = await response.json() as DummyUser;
return {operation: 'options.resolve', data: mapUserToOption(user)};
}, []);

return <DyonaForm
schema={schema}
initialValues={initialValues}
dataSourceExecutor={dataSourceExecutor}
onSubmit={(values) => console.log(values)}
/>;
}

The numeric user ID stays numeric from initialValues, through request.input.value, to option.value. Comparisons are strict, so do not convert an ID to a different type. On submit, DYONA Form returns {customerId: 1}, not the option label.

Next steps​