Fork me on GitHub
#re-frame
<
2022-12-15
>
Tema Nomad07:12:27

how to dispatch synchronously an effect-handler in tests? I am trying to use it like this:

(reg-event-fx
  :event-with-coeffect
  (fn [{:keys [db]} _]
    {:fx [[:dispatch-sync [:add-todo "Make a lunch"]]
          [:dispatch-sync [:set-showing :active]]]}))

...test body...

(let [todos (subscribe [:todos])
      showing (subscribe [:showing])]
  (dispatch-sync [:event-with-coeffect])
  (is (= (count @todos) 1))
  (is (= @showing :active)))
but todos and showing ratoms dont change

Tema Nomad09:12:50

yes but I try to use just cljs.test Because all other my frontend tests (events, subs) work w/o re-frame-test lib

Tema Nomad09:12:26

I thought there is a solution with just using re-frame

Dmytro Bunin09:12:44

oh I think dispatch sync is not allowed inside of the events. so I guess there is no effect with dispatch-sync maybe the problem is with that?

Tema Nomad09:12:37

maybe, IDK, thats why I ask it here) I tried also to use :dispatch inside of :fx vector - the same issue

Dmytro Bunin09:12:22

with dispatch it makes sense that it is failing as that is async. So I think you need to use the re-frame-test to make the flow synchronous.

Dmytro Bunin09:12:13

if you dont want to pull in dependency you can just mock the dispatch like they did in the library https://github.com/day8/re-frame-test/blob/master/src/day8/re_frame/test.cljc#L297

Dmytro Bunin09:12:51

but then you will need to figure out the state resets between test runs, so I think using the re-frame-test is easier

Dmytro Bunin09:12:56

also you can use it with clj.test

Dmytro Bunin09:12:03

it’s just a library with macros

Tema Nomad09:12:14

I see. Btw, dont you know why many clojure/script libs are not fresh? Like re-frame-test - last commit was 1 year ago

Dmytro Bunin09:12:32

it’s complete, so nothing needs to be added is my guess

👍 1
emccue16:12:21

I got nerd sniped into caring again

emccue16:12:33

just...don't dispatch from your events

emccue16:12:39

this is reason 1/N

emccue16:12:09

do all the state updates inline in :db and use :fx only for stuff that is side effects

Tema Nomad17:12:27

@U3JH98J4R I dont understand you, can you please rephrase?

emccue17:12:29

okay so in your :fx you have two dispatch-syncs

emccue17:12:40

what do :add-todo and set-showing do?

Tema Nomad17:12:15

its just an abstract example of some event with side-effect that is not only changes db, but also does some side-effect.

Tema Nomad17:12:49

but both events inside it change db, yes)

Tema Nomad17:12:22

add-todo adds a todo set-showing sets showing filter keyword (like :done)

emccue17:12:35

right - so if they are just state updates, just do them immediately

emccue17:12:14

events are not helper functions and they suck if you use them like they are

emccue18:12:47

(defn add-todo 
  [db todo]
  (assoc db :todo todo))

(defn set-showing
  [db showing-status]
  (assoc db :showing-status :showing-status))

(reg-event-fx
  :event-with-coeffect
  (fn [{:keys [db]} _]
    {:db (-> db
             (add-todo ...)
             (set-showing ...))]}))

👍 1
emccue18:12:05

now its pretty trivial to test if you just..

emccue18:12:00

(defn add-todo 
  [db todo]
  (assoc db :todo todo))

(defn set-showing
  [db showing-status]
  (assoc db :showing-status :showing-status))

(defn event:event-with-coeffect
  [{:keys [db]} _]
  {:db (-> db
           (add-todo ...)
           (set-showing ...))]})

(reg-event-fx
  :event-with-coeffect
  (fn [& args]
    (apply event:event-with-coeffect args)))

emccue18:12:11

call the function with a db, inspect the output

emccue18:12:37

now - lets say that add-todo wants to specify both a state update and side effects to perform

emccue18:12:11

(defn add-todo 
  [db todo]
  {:db (assoc db :todo todo)
   :fx [[:http-xhrio ...]]})

emccue18:12:22

still just make it all "happen" at once

emccue18:12:36

(defn add-todo 
  [db todo]
  {:db (assoc db :todo todo)
   :fx [[:http-xhrio ...]]})

(defn set-showing
  [db showing-status]
  (assoc db :showing-status :showing-status))

(defn event:event-with-coeffect
  [{:keys [db]} _]
  (let [{db' :db
         fx  :fx} (add-todo db ...)]
    {:db (-> db'
             (set-showing ...))]})

(reg-event-fx
  :event-with-coeffect
  (fn [& args]
    (apply event:event-with-coeffect args)))

Tema Nomad18:12:06

@U3JH98J4R thanks a lot, will try tomorrow

fabrao18:12:17

Hello all, what it means this error message? re-frame: Subscribe was called outside of a reactive context.

fabrao18:12:13

is it means that I have a subscription inside reg-event-db?

p-himik19:12:44

Yeah, or in some :on-click or a similar JS event handler, or in some effect handler, or at some other place. I think that warning has a stack trace that should help you navigate to the place where the sub is used.

fabrao19:12:44

@U2FRKM4TW so, in this way, is this should be a problem

(defn global-search-input []
[:input {... :value       @(rf/subscribe [::subs/global-search-value])})
?

p-himik19:12:32

No, this is the intended usage - there should be no warning here.

fabrao20:12:41

and something like this? The subscription inside the reg-event-fx :fx dispatch?

(rf/reg-event-fx
   ::show-select-reason-modal
   (fx [{:keys [...]} [_ _]]
     {:fx [[:dispatch [::delete-modal ....
          (let [reason-id (:id @(rf/subscribe [::archive-subs/selected-reason]))] ...

p-himik20:12:11

This will produce the warning.

fabrao20:12:31

Thank you :-)

👍 1
fabrao20:12:52

what is the effects doing this kind of things?

p-himik20:12:31

Not sure what you mean. But if you're asking about why the warning exists, that's because subscriptions are cached and using them outside of the reactive context leaks them in the cache. Maybe there were other issues, I don't really recall.

Tarmo Petrovits11:12:59

@U2FRKM4TW I see that the current solution needs reason-id . How would you get it from re-frame-db then? One solution is to pass it as an argument, but let’s say there is 5 level nesting of reg-event-fx , what could be the alternatives if any?

p-himik12:12:50

Event handlers have access to app-db - it's in their first parameters. As is in the case of reg-event-db and under the :db key in the case of reg-event-fx. Unless your subs are rather complicated, extracting the value then should be rather straightforward.

Tarmo Petrovits12:12:26

https://ericnormand.me/guide/database-structure-in-re-frame#evolving-a-database It’s a bit conflicting with that paragraph. I should be reading from DB with subscriptions and writing it with events . Or is it still okay to ask directly from DB on that case? And if this is okay, then is there any good pattern for complicated queries?

p-himik12:12:55

Yes, that particular section is about views, although it doesn't state it in an obvious way. It's completely OK to talk to the db from within an event handler - that's exactly what the db is there for, right in the first argument of a handler function. Also, instead of reading someone's very verbose understanding of re-frame, I'd suggest simply reading the official documentation and examples. This way, there's much less chance of misunderstanding.

p-himik12:12:10

> is there any good pattern for complicated queries? Depends on what you mean by "complicated queries". If you mean that a sub depends upon some other subs, and maybe even in a dynamic way, and then you want to also get that sub's value in an event handler, then no, unfortunately there's no good pattern for that. One way to achieve that would be to separate all pieces of the sub into smaller functions and then compose those functions separately in subs and in event handlers. Another way would be to use a global interceptor, compute the value in it when relevant inputs change, and cache it in the db. Then both the sub and the event handler can just access that cache.

Tarmo Petrovits12:12:53

Thank you, I got my answers 🙂

👍 1