This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-11
Channels
- # announcements (15)
- # aws (11)
- # babashka (13)
- # babashka-sci-dev (2)
- # beginners (63)
- # calva (20)
- # cider (9)
- # clj-kondo (27)
- # clojars (3)
- # clojure (34)
- # clojure-art (4)
- # clojure-europe (21)
- # clojure-filipino (1)
- # clojure-indonesia (1)
- # clojure-my (1)
- # clojure-nl (11)
- # clojure-norway (10)
- # clojure-sg (1)
- # clojure-spec (4)
- # clojure-uk (4)
- # clojurescript (5)
- # cursive (8)
- # deps-new (2)
- # events (1)
- # exercism (2)
- # fulcro (44)
- # graphql (6)
- # gratitude (1)
- # introduce-yourself (1)
- # jobs (3)
- # leiningen (5)
- # lsp (26)
- # membrane (18)
- # missionary (9)
- # off-topic (1)
- # pedestal (5)
- # portal (1)
- # quil (24)
- # re-frame (17)
- # reagent (5)
- # remote-jobs (2)
- # reveal (3)
- # spacemacs (4)
- # tools-build (1)
- # tools-deps (12)
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?Can you elaborate on your case?
You could:
1. transact!
from the result-action
2. Use state-machines
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 _]
(dr/route-deferred
[: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)]
(div
(button :.btn.btn-primary.btn-lg
{:onClick #(df/load! this :tab/ids TabHeader {:remote :protected
:target (targeting/replace-at [:component/id ::main :tab/ids])})}
"Refresh")
(nav
(div :.nav.nav-tabs#nav-tab {:role "tablist"}
(map ui-tab-header tab-ids)))
(div :.tab-content#nav-tabContent
(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.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 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 🙂
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?
Ok, I managed to replicate it... 👀
Noteworthy: if you remove the will-mount load, then the on-refresh load works as expected. So it is not wrong per se.
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
interestingly,
(merge/merge-component!
TabHeader
[{: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 expectedFYI 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.But when I log its output state when triggered by the button then it seems to have done nothing.
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: https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/algorithms/data_targeting.cljc#L142
@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)
@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
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
I can send a fix PR
@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.
Simply: it works if the target is nil
@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.
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 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])
@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.
thx for sharing!