Fork me on GitHub

Hi, if I have a component with ident [:person/id :person/id] and I want to have a dropdow with all the other persons in the db to select from. It would be nice to have a link query [:person/id '_]. How do I get the link query to coexist with the :person/id query?


@magra so technically you cannot in the query, since an ID must be at that key in the props. You could construct a list of idents as a top-level key like :dropdown/all-people that is a list of idents to people (which could also be one of your load targets). This has the added advantage of being able to reify the sort/filter of the options. You could look in app/current-state within render, but that will not refresh properly unless you also change shouldComponentUpdate to true on that component…and even then certain renderers (like ident-optimized) might not properly figure out what to refresh.


RAD uses the first approach, but further generalizes it to use an options (picker) cache with a configurable TTL. Thus 100 different rows in a table that all want the same options for a dropdown end up sharing that centralized cache.


The second approach is viable, and just puts all the logic in render. This avoids “cache staleness” but costs a bit more in render (which may be perfectly fine)


I have query troubles with df/load!... Let's say I have a component with ident [::ExampleView :example/id]. In will-enter I run the df/load!:

(df/load! app [:example/id id] ExampleView
  {:post-mutation `dr/target-ready
   :post-mutation-params {:target [::ExampleView id]}})
And it loads - but to the root key :example/id, instead of ::ExampleView. Leading to that the defsc has nothing in its props


You put the :target in the post-mutation-params, so it gets past to dr/target-ready. You need another target to target the load.


Yes, that is on purpose. I had the target for the load, but that made no difference.


(df/load! app [:example/id id] ExampleView
  {:target [::ExampleView id]
   :post-mutation `dr/target-ready
   :post-mutation-params {:target [::ExampleView id]}})
Should work but perhaps the two colons :: won't resolve correctly.


Or do you want to load into [:example/id id], not [::ExampleView id].


I want to load it into [::ExampleView id]. I thought since the query object's ident is already [::ExampleView :example/id] the :target wouldn't be necessary


Yea I tried with :target now, the only difference is that it places [:example/id #uuid "whatever-the-uuid-is"] inside ::ExampleView


I think you want to put it into [:example/id whatever] in the normalized db. That has nothing to do with the component.


I mean Fulcro places it there anyway


But how do I get my component to use it


Then your load ist ok.


I mean I can change the component ident to :example/id and it works, but that can't be the only way


It is the best way.


I think I changed it because I had problems with having more than one form with :example/id, and them existing, even if they weren't loaded simultaneously, gave me weird behaviour


You query for it and make shure that either your query composes from root or you query with a link query like [:example/id id].


The other worked fine after the ident change, but this one doesn't


I have lots of component using the same [:thing/id id].


If you twiddle query and ident on defsc I need to reload (F5)


Oh I know that. Oh well, guess I'll simply change the ident then. No time to investigate, there's loads of things to finish


@zilti The :target needs to be on the same level as :post-mutation-params, not beneath it.


Thank you Tony!


How does form/save-form* behave with refs of cardinality many? Is it possible to add/retract individual links? Because I am just trying to get save-form* working for the first time. Right now having problems with it telling me the transaction is empty


1. it does. 2. Just add/remove the data…being careful to make sure new ones have initialized form config. 3. by default tempids are used to figure out if the thing is new or not. Not new == minimal diff


1. Okay, then I guess I'll try to use that! 2. Yes, I looked at the code from the demo to enable/disable accounts. Guess I am missing something because of that "empty tx" error... Now sure where the tx info has to be added or whatever. 3. Good, yes in this case it is stuff that is already in the db. It is pretty much doing what the demo's enable/disable account thing does, but on a custom form


Right now this is how I trigger it:

(comp/transact! this
                                                    {:company/id (:company/id props)
                                                     :target-state (not (comp/get-state this :ui/approval-toggle-state))
                                                     :tenant-uuid (lib-form/tenant-uuid)})])
And in the mutation I do this:
   (pc/defmutation toggle-company-approve [env {:company/keys [id]
                                                :keys [target-state tenant-uuid]
                                                :as inputs}]
     {::pc/params #{:company/id}
      ::pc/output [:company/id]}
     (log/info "TOGGLE APPROVE, inputs:" inputs)
     (form/save-form* env {::form/id        id
                           ::form/master-pk :company/id
                           ::form/delta     {[:company/id id]
                                              (if target-state
                                                {:after tenant-uuid}
                                                {:before tenant-uuid})}}})
     {:company/id id})
   (m/defmutation toggle-company-approve [data]
     (remote [_] true)))


The exact error I get is first "Running txn []" and then "Unable to save form. Either connection was missing in env, or txn was empty."


I created simple mutations manually for now


target-state is a boolean?


your code doesn’t make sense to me…the name is “toggle”, so I would expect you to always be setting :after (boolean ___)


oh…perhaps :company/approved is who approved it.


Yes, exactly! Sorry. :company/approved is a ref with cardinality :many and I want to add/remove connections from that ref


Does RAD know it is a ref?


The values should be idents, not UUIDs


[:tenant/id tenant-uuid]


or whatever


and if it is to-many, then it MUST be a COMPLETE list of the idents


I think the built-in logic tries to make it match your new list of refs. It assumes you have all of them on screen, and the diff should say what this list was before/after


so really [[:tenant/id uuid]]


Ooh, I see.


So I'd have to first fetch all the values that are currently present


Then remove/add the one I want, and then give both those lists to RAD


yep…otherwise how could form-save ever know what to delete?


My conclusion was "since the id is in the :before and not in the :after"


well, if you read the logic, that might work…I don’t think it checks the db


so if you said {:before [] :after [[:tenant/id id]]} that would be an add


I will try. So my mistake was to throw UUIDs at it instead of :db/id values


Ah I have to give it tuples


what you put in the idents depends on how you’ve defined the attributes, but you have to give to-many as vectors of idents.


If your IDs are UUIDs then you’ll use UUIDs


Yes, they are


if your IDs are native, then you’d use db ids


The point is that in forms in the UI, you’d be managing exactly what you’d have in the normalized fulcro db


Thanks, I'll definitely try that in a bit!


The diff for form state is trivial: it is literally the diff (normalized) between what was in state before and after your changes.


Yes, I kinda assumed it is a beefed-up Form-State thing


not beefed up, just implemented


it understands exactly what form-state sends


so if you ever want to figure out a case, make a form, get it working, and look at what diff outputs…you don’t even need UI…all functions will work on state with ui-less defsc


See the tests, for example


I have a nested dynamic router, and one of the components under that dynamic router is throwing this error from its :will-enter when I go to its URL on a full page load (hyperlink after load is fine): "r/target-ready! was called but there was no router waiting for the target listed". is there something with my router setup that i might be doing wrong? sounds like the nested router was supposed to have finished some lifecycle events before my component fired the target-ready mutation. (BTW, that's getting triggered via a route-deferred load)


relevant code segment:

(defsc ProjectDetail
  [this {:project/keys [id name slug] :as props}]
  {:query         [:project/id :project/name :project/slug]
   :ident         :project/id
   :initial-state {}
   :route-segment (r/route-segment :project)
   :will-enter    (fn [app {:keys [id] :as route-params}]
                    (log/info "WILL ENTER: " (pr-str route-params))
                    (when id
                        [:project/id id]
                        #(df/load! app [:project/id id] ProjectDetail
                                   {:post-mutation        `dr/target-ready
                                    :post-mutation-params {:target [:project/id id]}}))))}


For debugging I tried setting a 3s timeout on the load!:

:will-enter    (fn [app {:keys [id] :as route-params}]
                 (when id
                     [:project/id id]
                     (fn []
                       (js/setTimeout #(df/load! app [:project/id id] ProjectDetail
                                                 {:post-mutation        `dr/target-ready
                                                  :post-mutation-params {:target [:project/id id]}})
...but I still got the same error.


nothing comes to mind as a sure-thing. Some notes from what I see: 1. Be careful how you use id. It is always a string when passed to you from URLs 2. Your route segment needs to be a vector that whose prefix is at least one distinct string among siblings.


Hm.. yeah, it's expected here for id to be a string, and the route-segment here is [:id]. Must be some small typo somewhere, i'm sure i'll find it eventually


It's written in the book: "If the given router is to be shown on initial startup, then these default routing targets must be singletons (have an ident that does not depend on their props)."


If I'm reading that right, that means it's expected that my component shouldn't work, since its ident is a function of its props? (namely the :project/id prop)


solved it!! I swapped the order of dr/initialize! and rfe/start! (the reitit function for hooking into the HTML5 history API)


the error makes sense in retrospect--the navigation event was triggering a DR route, and the routers weren't ready for it