In shadow-grove, triggering an effect from within an effect


So I want my single-page web app to load data, as one would expect. There is a top-level view component, and I thought it’d be nice and tidy to trigger the “get-data” effect from the mount-effect which I can “hook” into this top-level view component.

But I haven’t found any example of how this is done. And calling queue-fx feels wrong (maybe just from the naming). Anybody know? A tiny code snippet would go a long way.

Perhaps related to this is this: In an effect I may want to call other effects—and the same question applies, how do I call an effect from an effect?

Let me throw a high level concept thought at you, before showing some code.

The idea in grove is that your state/db drives the UI, not the other way arround. So, from your data you can infer that you are going to render “top-level view”, so even without rendering you can infer that you need to “get-data”. So, in theory you don’t need mount-effect at all.

Well, thats the theory at least. It is still lacking a good way to express this properly. So, for now mount-effect seems like the obvious candidate, although not what its meant for.

You can trigger effects from mostly anywhere (eg. async events) via (sg/run-tx! rt-ref {:e ::foo!}). The only exception where this is not allowed is from inside other events. mount-effect is not an event though, so this is fine.

Define what you mean by “effect” here? ie. a mount-effect cannot trigger another effect? Do you mean event?

Thomas, you’re absolutely right: I shouldn’t be using mount-effect to trigger the data load.

To clarify my question about how to call an effect from an effect?— The effect here is the reg-fx kind. I suppose I call queue-fx, right? Also: you write that I can trigger effects via run-tx—but isn’t run-tx used to dispatch events, not effects?

(BTW, any interest in calling these reg-effect and queue-effect? :slight_smile: )

I still don’t know what your question is.

There is a difference between fx and tx. tx as in transation is the processing of a given event, in the context of a “database transaction”. Meaning you can make changes to the database and they’ll all be committed at the end. fx triggers after this transaction finishes and the db has been updated.

If you want to trigger a new tx from an fx handler, which for example did an async request and has a result it wants to return you can use the run-tx! example from above or the transact! special provided to the fx-env.

(sg/ref-fx :my-side-effect
  (fn [{:keys [transact!] :as env} fx]
    (js/setTimeout #(transact! {:e :timeout!}) 1000))))

This will just trigger the {:e :timeout!} event after 1sec for any event that did (sg/queue-fx env :my-side-effect {}).

So, no I’m not interested in changing the naming here. They are two distinct things, with mount-effect/render-effect being something else entirely again. fx are free to call whatever functions they want. So, you could just call another fx function yourself. grove does not provide any additional facilities to do that for you.

If you want to call another tx handler within from a tx handler the idea is to call a function directly.

Suppose you have

(sg/reg-event rt-ref ::foo! (fn [env ev] ...))
(sg/reg-event rt-ref ::bar! (fn [env ev] ...))

but ::bar! wants to also do whatever ::foo! does, then you just restructure the code.

(defn foo! [env ev]
   (do-foo-things env))
(defn bar! [env ev]
  (-> env
      (foo! ev))) ;; or a new map, depends on what `foo!` expects.
(sg/reg-event rt-ref ::foo! foo!)
(sg/reg-event rt-ref ::bar! bar!)

If you are looking for something like re-frame :dispatch fx, then it doesn’t exist. The reason it doesn’t is because I think this whole reg-event business with a bunch of anon fns is not great. So, instead the plan is to just slap some metadata on regular defn and have the system recognize and register these events. I’m doing this in the shadow-cljs UI code, eg.

so wherever you see the ::ev/handle metadata, that’ll turn into a (sg/reg-event rt-ref ::init-data that.ns/init-data) at another point. This makes it a easier and apparant that you can just call one tx handler from another and doesn’t leave so many anonymous functions all over the place. I don’t think you need :dispatch at that point.

Unfortunately doing this metadata business in CLJS is currently a bit awkward, so I’m still trying to figure out to make it less so. Until then, you can just do the reg-event call manually with a normal defn.

So let me see if I understand all this: I will put it in my own words, and please correct me where I’m wrong.

There is an event and an effect. The chief distinction is that an event—which is registered using reg-event—returns a modified env, whereas an effect—which is registered using reg-fx—does not return a modified env.

An event is “transacted,” and an effect is “triggered.” From my view components, I can run an event transaction, but I cannot trigger an effect (nor should I wish to do so).

Now how to transact an event: via run-tx or run-tx!, depending on whether I am doing it from a view component or elsewhere, respectively.

And how to trigger an effect: via queue-fx. I don’t know but the following feels right: an effect should only be triggered by an event—do you agree? I am thinking of an effect simply as a “side effect”—that is to say, the “impure” part—of an event’s being transacted.

Finally there is the matter of the transact! function which env comes with: should I wish to transact an event from an effect, I do one of these (this is my own function, not grove’s):

(defn run-tx-async
  "Dispatches, using the transact! fn that is included in env, the given event vector evt;
   dispatches using a timeout in order to untether it from the current effect transaction."
  [{:keys [transact!] :as env} evt]
  (js/setTimeout #(transact! evt) 0))

So, is this all kosher?

Not quite, the description is inaccurate mostly because of confusing terminology. I don’t blame you because the docs (non-existent) don’t make this particulalry clear. reg-event is a bad name, I used this to stay similar to re-frame in concept but it confuses you here it seems.

There are events. They originate usually from user actions. Such as clicking a button. They are typically from the DOM, but could be from the network, or other some thing that happens at any given time.

A DOM event is first dispatched to the owning component. If a component handles it it may call dispatch-up to make the even travel further up (ie. go to the parent, then its parent, etc), while also potentially changing it. The component may also just run-tx directly, which starts a database transaction. If the owning component didn’t handle it the default is to travel up. If no component handled the event it is turned into a database transaction call, sort of a fall-through.

This is where reg-event comes into play. These are actual db transaction handlers. Components themselves cannot directly modify db, which is why run-tx exists. This is a design choice I’m questioning a little, and it may change. But maybe a better name would have been reg-tx. In some sense the thing passing through is still an event, but it is not longer a component event it is a db event. I realize this is probably confusing but these are separate things. Events sometimes need to take stuff out of the DOM, and only components can do that. Database transactions should not have any knowledge of the DOM, thus they can’t even access it.

The tx or db transaction event may queue side effects or fx to be executed when the updated db state is “committed”. Just like re-frame the intent is to have a data description of something that is supposed to happen in a somewhat controlled way. With how they happen being configurable.

To simplifiy so far we have three things: event, tx, fx. Which the docs should hopefully reflect at some point.

You keep saying “effect”, which I guess you mean “fx” here. Since it is confusing to have multiple definitions of something I generally consider “effect” to be component things, ie. mount-effect for doing things then a component mounts, or render-effect for doing stuff when it rendered. Again entirely different concept, not related in any way to the previous three. Naming is hard. :stuck_out_tongue:

So, to get back to your question then yes. A fx handler can use the transact! it receives from its env argument, which will trigger a new db transaction.

Really appreciate your reply (as always). The funny thing is that presently I am using shadow-grove and running up against zero blocking issues in my understanding. This back-and-forth, I now reflect, has been more of finding the right words to explain everything to myself.

The “naming challenge…” I love that part of software design; finding the right nouns and verbs, etc.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.