day8 / re-com

A ClojureScript library of reusable components for Reagent
https://re-com.day8.com.au
MIT License
796 stars 147 forks source link

Bizarre issue with the typeahead component #330

Closed jimpil closed 12 months ago

jimpil commented 12 months ago

Hi there,

I seem to be having the strangest issue with the following simple component. I've spent several hours looking at this, and I've reached a point where I fairly certain that I'm doing everything correctly. Ok so imagine the following component:

(defn tab-content [{:keys [id base-unit base-definition choices]}]
  (let [current-base-unit @(rf/subscribe [::subs/current-base-unit])
        current-n         @(rf/subscribe [::subs/current-n])
        base              (or current-base-unit base-unit)
        n                 (or current-n 1)]
   [rc/h-box
     :src (at)
     :gap "1em"
     :children [[rc/typeahead
                  :src (at)
                  :suggestion-to-string get-label      ;; won't accept keyword
                  :render-suggestion    get-label      ;; is documented to accept 2 args
                  :change-on-blur?      true           ;; is documented to default to true
                  :model {:id base :label (name base)} ;; is documented to be optional
                  :data-source (partial suggest-from choices)
                  :debounce-delay 200
                  :on-change #(rf/dispatch [::events/base-unit-selected (:id %)])]
                [rc/input-text
                  :src   (at) 
                  :model (str n)
                  :on-change #(rf/dispatch [::events/n-selected (parse-double (str/trim %))])]]]))
;; relevant events
(rf/reg-event-fx ::base-unit-selected (fn [{:keys [db]} [_ unit]]  {:db (db/with-base-unit db unit)})) ;; (assoc db :current-base-unit id)
(rf/reg-event-fx ::n-selected         (fn [{:keys [db]} [_ n]]     {:db (db/with-n db n)}))            ;; (assoc db :current-n n)
;; relevant subs
(rf/reg-sub ::current-base-unit :-> :current-base-unit)
(rf/reg-sub ::current-n         :-> :current-n)

The typeahead component works perfectly in isolation (despite being incorrectly documented in various places - see comments on the snippet above). However, when a value is selected on the input-text, the typeahead loses its selection! I've included the relevant events/subscriptions in the snippet as proof that ::events/n-selected doesn't do anything unusual. In fact, I have console-logs and re-frisk telling me that no value has been lost from the db, so I am at a complete loss here - mainly because i spent quite a bit of time to make the typeahead work (the outdated docs didn't help there), and now this happens from a different/completely-unrelated component!

If you can provide any hints or clues, that would greatly appreciated! Many thanks in advance...

jimpil commented 12 months ago

FYI, i solved my problem by pushing the subscriptions deeper (i.e. closer to their respective consumers), like this:

(defn unit-typeahead
  [{:keys [base-unit choices]}]
  (let [current-base-unit @(rf/subscribe [::subs/current-base-unit])
        base              (or current-base-unit base-unit)]
    [rc/typeahead
     :src (at)
     :suggestion-to-string get-label      ;; won't accept keyword
     :render-suggestion    get-label      ;; is documented to accept 2 args
     :change-on-blur?      true           ;; is documented to default to true
     :model {:id base :label (name base)} ;; is documented to be optional
     :data-source (partial suggest-from choices)
     :debounce-delay 200
     :on-change #(rf/dispatch [::events/base-unit-selected (:id %)])]))

(defn unit-amount []
  (let [current-n @(rf/subscribe [::subs/current-n])
        n         (or current-n 1)]
    [rc/input-text
     :src   (at)
     :model (str n)
     :on-change #(rf/dispatch [::events/n-selected (parse-double (str/trim %))])]))

which lets me write my original component like so:

[rc/h-box
    :src (at)
    :gap "1em"
    :children [[unit-typeahead quantity]
               [unit-amount]]]

I'm still not sure what was wrong before, but I guess it's good practice to do that anyway, so I'm happy.