Skip to content
Devix Open Source

Guide

Remote data

Point load at your endpoint. The widget debounces the typing, cancels the request that is no longer wanted, caches what came back, and pages as the list is scrolled.

createSelect(input, {
  name: 'customer',
  minChars: 2,
  debounce: 200,
  load: async (query, { signal, cursor }) => {
    const response = await fetch(`/api/customers?q=${encodeURIComponent(query)}&page=${cursor ?? 1}`, { signal });
    const body = await response.json();
    return {
      options: body.data.map((row) => ({ value: String(row.id), label: row.name, description: row.email })),
      cursor: body.next_page,
      hasMore: Boolean(body.next_page),
    };
  },
});

Return either a plain array of options or { options, cursor, hasMore }.

What happens for you

  • Debounce. One request per pause, not one per keystroke.
  • Abort. The signal is aborted when a newer query starts. Throwing AbortError is expected and never shown as an error.
  • Cache. Repeating a query — including going back to an earlier one — is answered from memory.
  • Paging. When hasMore is true, reaching the end of the list calls load again with your cursor.
  • States. The widget carries dxs--loading while a request is in flight and shows your strings.error when one fails, so the list never lies about what it knows.

Keeping the chosen option

A remote list only holds what the last query returned, so pass the already-chosen option in when you render the field — either as the <select>'s existing <option>, or:

createSelect(input, { options: [current], value: current.value, load });

Laravel

Route::get('/api/customers', function (Request $request) {
    return Customer::query()
        ->when($request->string('q')->toString(), fn ($q, $term) => $q->where('name', 'like', "%{$term}%"))
        ->paginate(30, ['id', 'name', 'email']);
});

The paginator's next_page_url is a cursor; hand it back as cursor and the widget pages through it.

Updated 12 Sep 2026