Fork me on GitHub

Setting (comp/transact! this [...] {:optimistic false}) ensures that all full-stack mutations within the transaction be processed sequentially. However, if I have multiple comp/transact! calls:

(comp/transact! this [(my-mutation)] {:optimistic false})
(comp/transact! this [(my-mutation)] {:optimistic false})
Then the action of the second my-mutation can happen before the result-action of the first my-mutation does. Is there a way to ensure total pessimistic -- where even across multiple transactions, the system will only process each mutation after the previous one finishes its full-stack round-trip?

Björn Ebbinghaus12:03:34

Can you elaborate on your case? You could: 1. transact! from the result-action 2. Use state-machines

❤️ 1

Hi there, I have some problem trying to use targeting/replace-at since it's not doing what I expect, and I'm wondering if someone can have a look through this piece of code and tell me if I've made a mistake somewhere.

(defsc Main
  [this {tab-ids :tab/ids :as props}]
  {:ident         (fn [] [:component/id ::main])
   :route-segment ["main"]
   :query         [{:tab/ids (comp/get-query TabHeader)}]
   :initial-state {}
   :will-enter    (fn [app _]
                      [:component/id ::main]
                      #(df/load! app :tab/ids TabHeader {:remote               :protected
                                                         :target               (targeting/append-to [:component/id ::main :tab/ids])
                                                         :post-mutation        `dr/target-ready
                                                         :post-mutation-params {:target [:component/id ::main]}})))}
  (let [tab-ids (map-indexed (fn [i e] (if (= i 0) (assoc e :ui/is-first? true) e)) tab-ids)]
      (button :.btn.btn-primary.btn-lg
              {:onClick #(df/load! this :tab/ids TabHeader {:remote :protected
                                                            :target (targeting/replace-at [:component/id ::main :tab/ids])})}
        (div :.nav.nav-tabs#nav-tab {:role "tablist"}
             (map ui-tab-header tab-ids)))
           (map ui-tab-body tab-ids)))))
Specifically, when I press the button the data is loaded and normalised correctly, but somehow the edge at [:component/id ::main :tab/ids] remains the same. As a quick test I tried targeting/append-to and it works as expected. Thanks in advance.

Jakub Holý (HolyJak)14:03:13

I'm confused. The code uses append-to and you say that works. So to relocate your problem we need to change that to replace-at? Is tab/ids a single thing or a vector? (In the latter case :what are you trying to do? Do you need to add also an index to ur replace at vector?)


Hey. Sorry about the confusion. :tab/ids is a vector of {:tab/id "<some-id>"}. So the workflow is this: when Main is loaded it will send a query to fetch all the tabs from the server (hence why the df/load in :will-enter uses append-to since at this stage there are no tabs stored locally yet) Now at some point I want to be able to refresh my data, hence why there's a button that will load all the tabs again. But in this case, because I already have all the old tabs stored from the previous load, I want to be able to replace all of them with the new data.


I hope that makes sense.

Jakub Holý (HolyJak)15:03:02

I see. You are sure the load is happening and returned the data you expected? Try to change the replace at location to something else, eg end with :tab/ids2 - will it appear there?


Hey. Yep the data is loaded correctly. I checked both via Network and the DB as well. The tabs are normalised into their respective table correctly. It's just for some reason I can't replace the edges. I'll give :tab/ids2 a go tmr 🙂


Hey so I tried it. No luck. The edges still aren't being replaced 😞

Jakub Holý (HolyJak)12:03:49

So you changed the replace-at vector to end with :tab/ids2 but the data is not placed under that key? What if you change the vector to just [:tmp-test/tabids] ? Perhaps st. is wrong with the replace-at syntax? What id you do merge or merge-component instead, does it work there?

Jakub Holý (HolyJak)15:03:15

Ok, I managed to replicate it... 👀

Jakub Holý (HolyJak)15:03:12

Noteworthy: if you remove the will-mount load, then the on-refresh load works as expected. So it is not wrong per se.

Jakub Holý (HolyJak)15:03:51

If I change the refresh load to :target (targeting/replace-at [:component/id ::main :tab/ids2]) then it DOES work and sets tab/ids2 in the Main component

Jakub Holý (HolyJak)17:03:16


    [{:tab/id 5 :tab/name "Home 3"} {:tab/id 6 :tab/name "Details 3"}]
    :replace [:component/id ::main :tab/ids])
seems to work and replaces the :tab/ids as expected

Jakub Holý (HolyJak)17:03:34

FYI at the end of load! , finish-load! is called which again calls

(targeting/process-target <current state map>
      :tab/ids [:component/id :com.example.ui/main :tab/ids])
which is understandable and works just fine, if I call it manually.

Jakub Holý (HolyJak)17:03:35

But when I log its output state when triggered by the button then it seems to have done nothing.

Jakub Holý (HolyJak)17:03:15

I think I see the problem - replace-at seems to DO NOTHING of the target is a vector (you expect it to replace it whole but it only supports prepending / appending to it). See here:

Jakub Holý (HolyJak)17:03:02

@U0CKQ19AQ is ☝️ correct, i.e. replace-at cannot replace a whole to-many property such as :tab/ids [[:tab/id 1] ...] ? I guess the solution would be to add a custom post-mutation that first clears the target property and then applies the normal behavior (which will work for a nil property)

Jakub Holý (HolyJak)17:03:35

@U013F1Q1R7G a simple solution: `{:target (targeting/replace-at [:component/id main :tab/ids])}` -> {:target [:component/id ::main :tab/ids]} i.e. don't use replace-at


seems an oversight to me


I don’t remember any special case that would make using a replacement on to-many not work right…I think I just missed the case


@U0522TWDA hey sorry for not getting back to you sooner. Somehow when I was testing against both tab/ids and tab/ids2 there were no changes to the state map at all despite transactions were being sent and responded properly. May be I've mucked up somewhere along the way 😅 I'll give {:target [:component/id ::main :tab/ids]} a go when I'm back to my main PC. Thanks a lot for going to the bottom of this! I really appreciate it 🙂


Also I'm a bit confused as to how removing the will-enter load makes the refresh load work again.


In my mind, the workflow is pretty simple: User load Main -> fetch, load and merge data before entering -> user manually refreshes data -> (here I'd expect it's exactly the same as before:) fetch, load and merge data.

Jakub Holý (HolyJak)18:03:13

Simply: it works if the target is nil

Jakub Holý (HolyJak)21:03:26

@U013F1Q1R7G Tony has release the fix that will make replace-at work for you in Fulcro 3.5.15 so switch to that


@U0522TWDA @U0CKQ19AQ Thank you so much for the help 🙂 I'll give it a go this evening.


Is there a way to prevent a mutation from being run twice concurrently? For example, if I put the transact! calls to the same mutation on two different buttons, and the user clicks buttonA and then buttonB, the same mutation will be fired twice in concurrence. Is there a way to prevent this from happening?


there is no such thing as “concurrent” in Fulcro client-side space. Those two clicks will run sequentially. Guaranteed. The mechanism doesn’t “debounce” your requests, as that makes no sense. You asked for it. Fulcro does it. Idempotence or using a debouncer is your answer.


(goog functions has a debounce)

❤️ 1
Jakub Holý (HolyJak)15:03:14

Fulcro cannot do it, it is very use-case specific. In some apps, running the same mutation twice in a sequence is fine (e.g to increase a counter as in the Book). As Tony suggests, if it is not ok for you, use debounce or make the mutation set some toggle to let it know to ignore subsequent calls.....


I think making sure that a mutation is idempotent is one way to ensure that even if this happens, it wouldn't result in anything bad. However, I'm still curious to know if there's a way to prevent this from happening in the first place


Is there a way to enforce a hot-reload of a (hooks/use-memo) or (hooks/use-callback) after modifying them?


have no idea what you mean. React hooks are a React feature. Fulcro has nothing to do with them.


If you mean “can I choose to have them change?“, see the React docs


If the answer to my “interpretation” is “yes”, see arity 2


If your question is instead “Can I get them to change after a source code hot reload”, then the answer is probably “no”…unmount/remount the component will do it, and you could change the react key on the component to cause that.


As a hack, you could add a hook to hot reload that simply increments a global atom holding an int, and include that int in your dependency list for use-memo:

(use-memo (fn [] ...) [@hot-reload-counter])

👍 2
😻 1

@U0CKQ19AQ Yes, I meant the latter. And the "hack" is exactly what I needed.


Is it true that the update-handler in a custom remote does not always invoke the progress-action? I counted the number of calls to update-handler and the number of times progress-action runs, and they are off by a lot.


I see. Reading com.fulcrologic.fulcro.algorithms.tx-processing it seems that the progress-action will only be called once per a process-queue! call, which might happen after several consecutive update-handler calls.

❤️ 1