Fork me on GitHub
#re-frame
<
2022-05-10
>
fadrian09:05:11

I am attempting to use the typeahead component from re-com in a re-frame app. To get the suggestions for the typeahead, I dispatch an event which triggers an http-xhrio request as follows:

(rf/reg-event-fx
 :suggestions
 (fn [{:keys [_db]} [_ s]]
   {:http-xhrio {:method          :get
                 :uri             (str "" s)
                 :headers         {"Accept" "*/*"}
                 :timeout         3000
                 :response-format (ajax/text-response-format)
                 :on-success      [:got-search]
                 :on-failure      [:got-error]}}))
The results of this request dispatch to another event:
(rf/reg-event-db
 :got-search
 (fn [db [_ result]]
   (assoc db :search-error nil :search-result result)))
I then access the suggestions result using a subscription:
(@(rf/subscribe [:search-result]))
This should work (and the get-suggestions call below does seem to work), but the subscribe call returns the previous value of the suggestions until the :got-search event has finished. Theoretically, the typeahead component's dropdown should change when the subscription changes, being a reactive component, but instead, it continues to display stale data. This is the typeahead code that I am using:
(defn get-suggestions [s]
  (rf/dispatch [:suggestions s])
  (@(rf/subscribe [:search-result])))

[typeahead
  :data-source (fn 
                 [s]
                 (filter #(s/includes? (s/lower-case %) (s/lower-case s)) (map #(first (s/split % "|")) (get-suggestions (s/lower-case s)))))
  :model @(rf/subscribe [:typeahead-state])
  :immediate-model-update? true]
Any ideas on why the typeahead dropdown isn't changing and how to get it to work? The dropdown seems to work when I return a static list of suggestions from the get-suggestions call, but not when I use the subscription.

p-himik10:05:28

Have you seen the example at https://re-com.day8.com.au/#/typeahead Specifically, how the "Asynchronous" parameter is implemented.

fadrian11:05:46

I've tried using the asynchronous callback function. It doesn't seem to work. The characters I type show up in the typeahead box, but the get-suggestions function doesn't seem to get called in that case.

p-himik11:05:15

Can you show the source code of how you used the async version?

fadrian13:05:41

Yes...

[typeahead
  :data-source (fn 
                 [s cb]
                 (cb (filter #(s/includes? (s/lower-case %) (s/lower-case s)) (map #(first (s/split % "|")) (get-suggestions (s/lower-case s))))))
  :model @(rf/subscribe [:typeahead-state])
  :immediate-model-update? true]
get-suggestions should return a list of suggestions that are passed to the callback function. However, the dispatch has not had time to go through its processing and so the callback gets passed the previous list of suggestions. This may be why this is not working.

p-himik19:05:49

Don't call cb immediately. As per the docs: > For the second case, the fn should return nil, and eventually result in a call to the callback with a collection of suggestion objects. That "eventually" should happen when your effect actually gets the data.

fadrian20:05:11

Yes. I eventually figured out what was going on... First, I struggled with getting the callback function to a place where the results were. I finally found out you could pass additional arguments through the http-xhrio request as follows:

(rf/reg-event-fx
 :suggestions
 (fn [{:keys [_db]} [_ s cb]]
   {:http-xhrio {:method          :get
                 :uri             (str "" s)
                 :headers         {"Accept" "*/*"}
                 :timeout         3000
                 :response-format (ajax/text-response-format)
                 :on-success      [:got-search cb]
                 :on-failure      [:got-error]}}))
which then allowed me to call the callback function after the results had been saved. The final piece of the puzzle was realizing that I was returning the string representation of a vector of results from the server instead of a vector of results. Using clojure.edn/read-string took care of that and the solution is now working:
(rf/reg-event-db
 :got-search
 (fn [db [_ cb result]]
   (assoc db :search-error nil :search-result (edn/read-string result))
   (cb (edn/read-string result)))
It uses the typeahead declaration:
[typeahead
  :data-source (fn 
                 [s cb]
                 (rf/dispatch [:suggestions s cb]))
  :model @(rf/subscribe [:typeahead-state])
  :immediate-model-update? true]

đź‘Ť 1
manutter5112:05:53

This one’s a stumper: on my re-frame-based login screen, I have a username field, a password field, a “Sign On” button, and a “Forgot password” link. When I log in, Chrome offers to save my username and password, but the last character of the password is missing. That is, if I log in as “User” with a password of “Password,” Chrome password manager will save that as “User” and “Passwor”. Which obviously won’t work the next time I go to log in. Anybody ever hear of such a thing?

vanelsas13:05:37

If I had to guess it would be a problem with processing the password input field, but difficult to give a proper answer without seeing code. Could it be the last character of your input field isn't processed properly the moment you are pressing the sign on button (or perhaps pressing return)? iow, are you 100% sure the entire password is used during registration and login?

manutter5113:05:03

Yeah, I can see in the dev tools inspector that the complete password is present as the value of the field, and network tools shows that the complete password is present when logging in. 🤷

manutter5113:05:11

And the correct number of bullets shows up in the field as I’m typing in the password.

vanelsas13:05:05

If I can't find a problem with Reframe I usually clear the browser cache and start the app again. Other than that I have no idea either. Good luck with it

manutter5113:05:53

Yeah, I’m doing that a lot, but so far I can’t pin it down.

manutter5113:05:10

I am using a custom/in-house field rendering component to generate the password field, but it’s the same code as for a regular text field, with the type set to “password,” and none of the other fields are having a problem with truncating the last character. So, again, 🤷

Dr. Moltu17:05:47

Hi, so based on re-frame docs, cofx are the way of passing input to event handlers to keep it functional and all of that, it's also how you interact with the outside world. What type of computation is allowed in a registered cofx? only light comps? is it ok to do IO? data from network? CPU intensive work? if yes, it blocks the UI and re-frame's event handling?

p-himik19:05:55

There's no sync IO in the browser. And cofx can't be async.

Dr. Moltu21:05:27

Thanks for your reply, so are you saying that cofx should be light like event handlers or extractors?

p-himik21:05:05

I don't know what you mean by "light". But cofx handlers can't deal with IO.

Dr. Moltu11:05:53

hi, by light I mean "not CPU intensive work", since cofx can't be async to keep the order of events execution from what I understand from the source code. What was confusing to me in the documentation, it says "Coeffects - the data your event handler requires from the world in order to do its computation", without mentioning how to generate that data (they give example of a random number, dates, local storage. all are pretty fast to generate), maybe I'm missing something, that's why I asked.

p-himik11:05:07

"Async" and "CPU intensive" are orthogonal though. A coeffect can easily have something CPU-intensive it in, but that would lead to poor UX since the UI will be frozen for the whole time that coeffect is running (not an issue if that can happen between two frames, of course).

Dr. Moltu12:05:00

Exactly, that's exactly my point, I was experiencing with a slow version of Fibonacci, and i calc fib(38) in a cofx, the entire thing freezes. So maybe it should be said in the docs that a cofx should be fast enough to not freeze the UI. Thanks for your input again.