re-frame

Henrique Prange 2023-09-21T21:29:29.247619Z

Hey guys! I’m working on a typeahead component that filters a list as the user types. Each letter triggers a request for filtered results from the server, with debounce to avoid too many requests. However, occasionally, multiple requests are still sent. If the first request is slow (like in the diagram), our app displays outdated results. What’s the best way to prevent the first request from overriding the second request’s result?

Omar 2023-09-21T22:16:37.955649Z

I had a somewhat similar situation at one point and my lazy hack was to store the last request and abort it if a new one were to take place. Not the best, but it works.

p-himik 2023-09-21T22:20:24.826349Z

Three solutions that I know of: • The mentioned above, with canceling requests (I assume "not the best" means that the implementation wasn't the best because the approach itself is IMO actually the best) • Track the ordinal of the request and only respect the results of the request with the right ordinal • Same but track input arguments instead

Henrique Prange 2023-09-21T22:31:57.656989Z

Got it! Since I’m using re-frame-http-fx, I think I can use the :on-request handler to track requests (as explained in the link below), and if a new request comes in, I can call the abort method on the previous one. Does that sound right? https://github.com/day8/re-frame-http-fx#optional-handler-for-on-request

p-himik 2023-09-21T22:33:57.110539Z

Yeah. You just have to make sure that you're aborting requests for that specific component for that specific task and not something else.

Omar 2023-09-21T22:38:24.634919Z

depending on the result size you send back, I'd personally probably store each request and match that up with the current input, then clear all the results on blur or selection or whatever. that way when a user backspaces, if the results were there it'd show immediately.

👍 1
Henrique Prange 2023-09-21T22:39:35.628259Z

Sounds like a good idea. Thanks!

Henrique Prange 2023-09-21T22:40:15.701639Z

Just curious, how would you track the order of requests in the second approach? I was thinking of using a timestamp to see if the response is too old and should be ignored. Is that what you had in mind?

Henrique Prange 2023-09-21T22:40:53.465659Z

I mean, the “Track the ordinal of the request and only respect the results of the request with the right ordinal” option.

Omar 2023-09-21T22:41:44.823809Z

Something like this would be really easy to deal with:

{:autocomplete {"a" [<results>]
                "abc" [<results>]}}

p-himik 2023-09-21T22:43:08.192279Z

@hprange Nah, just have an integer value somewhere and only increment it whenever a new request comes in. The idea is stolen from here, where that integer is named "epoch": https://lucywang000.github.io/clj-statecharts/docs/integration/re-frame/#discard-stale-events-with-epoch-support

p-himik 2023-09-21T22:43:41.684219Z

But for typeahead, I actually like Omar's solution with storing the results.

Henrique Prange 2023-09-21T22:45:23.730489Z

Yeah, it seems like a solid choice, especially because we can cache the results if the user goes back and edits their input.

p-himik 2023-09-21T22:47:37.099289Z

I'd just make sure that it's a limited LRU cache or something similar.

Henrique Prange 2023-09-21T22:50:34.393449Z

Got it! Now I’ve got what I need to make an informed decision. Thanks for the assistance, folks!

👍 1
DrLjótsson 2023-09-22T18:15:41.956429Z

In the event that dispatches the http effect, you increase a counter in app-db and you include that counter in the event vector that is dispatches when the http event completes. If the count in that event vector and app-db match, you use the returned value, otherwise discard it. EDIT: Sorry, I hadn’t read the whole thread (forgot to scroll 🤯 ), I was describing the same solution that @p-himik linked to.

👍 2