To be honest, I just implement one when I need it in a project. You can do it without re-frame, which I like to do for something this simple:
- Two components, one for the input control, one as a wrapper
- Local state atom for the input control, with the usual
on-change swap for
- Local state atom for the typeahead, storing loading status, data and flags for what’s visible
- Typeahead passes a debounced on-change for the input, which the input wraps to facilitate the direct render from its local state. The debounced on-change is created via goog.functions/debounce and triggers the fetch logic for the typeahead
- Result rendering is done in the typeahead, optionally in a third component
You can also pass in fetch configuration and/or render functions to the typeahead, just pass them as props. Makes for a nice re-usable component.
If you’re bent on using re-frame for this, I’d still recommend using an atom for the input control, and some sort of ID for each typeahead instance to track its state via re-frame. I like to use a globally defined goog.ui.IdGenerator and a helper function to get one (think it’s cheaper than
(random-uuid) or sth):
(:require [goog.ui :as ui]))
(defn next-id 
When a typahead is created, it creates an ID for itself and “scopes” respective re-frame events by it. You can use this to keep results for each instance apart, and/or pass in a fixed ID via props to sync/share/persist the typeahead state.
Don’t know if it’ll help much, but this is sort what I’m talking about (although I wrote it in one go and didn’t run anything from it ): https://gist.github.com/madbonkey/baf1417be520b27cf94a1fb14637ecbe