Autocomplete Widget with Dynamic Options Fetching

Hi, this is something I’ve always wanted to implement but couldn’t quite work out how.

Say I want to use an AutocompleteInput widget but I want the options to be fetched dynamically from a search of that input.

E.g. the user inputs “hello” and I have an API endpoint that I can call with this string that will return a list of potential matches. I.e. an external search method.

I wondered about subclassing the widget and adding a depends on the value_input e.g.

class FetchAutocomplete(pn.widgets.AutocompleteInput):
    """Widget to autocomplete by fetching options."""

    @pn.cache(ttl=3600, max_items=25)
    async def _fetch_options(self, value: str) -> list[str]:
        """Fetch the options for the widget."""
        try:
            async with MyClient() as client:
                response: dict = await client.find(value) 
        except httpx.HTTPError as error:
            return []

        return [result["name"] for result in response["results"]]

    @param.depends("value_input", watch=True)
    async def _update_options(self):
        """Update the options for the widget."""
        if len(self.value_input) < self.min_characters:
            return

        self.options = await self._fetch_options(self.value_input)

Now this somewhat works but the problem is that this is not in sync with the user input. As soon as the input is entered it does the search against the current options. The options are updated they are not refreshed on the input. Enter one more character and then it will present the options that match from the previous search.

This makes sense to me as to what’s going on, but I can’t quite work out how to trigger a refresh of the matches after the options have been updated, if it is even possible?

Of course there might be a better way to do this completely?

Thanks for any help with this.

I think you might have to introduce some JS callback, or use JSComponent.

A reproducible version of your code, albeit still not working as intended.

import panel as pn
import param
import asyncio
import httpx

pn.extension()

class FetchAutocomplete(pn.widgets.AutocompleteInput):
    min_characters = param.Integer(default=2)

    @pn.cache(ttl=3600, max_items=25)
    async def _fetch_options(self, value: str) -> list[str]:
        """Simulate fetching options from an API."""
        await asyncio.sleep(0.2)  # Simulate latency
        return [f"{value}_{i}" for i in range(5)]

    @param.depends("value_input", watch=True)
    def _on_input_change(self):
        if len(self.value_input or "") >= self.min_characters:
            asyncio.create_task(self._update_options_and_refresh(self.value_input))

    async def _update_options_and_refresh(self, value):
        # fetch options
        new_options = await self._fetch_options(value)
        self.options = new_options


# Usage
autocomplete = FetchAutocomplete(placeholder="Type to search...")
pn.Column(autocomplete).show()

Maybe you can ask some LLM to somehow introduce some JS callback to expand the options on typing.