Fork me on GitHub

Should we update the of the Developers Guide? Things I have in mind: • Code example on how to plug in a specific renderer. • Possibly update the current state of things? I.e. I've;cid=C68M60S4F that multi-root renderer is the default, but the other day Tony;cid=C68M60S4F it should be considered deprecated. FDG states that ident-optimized is the default, but that we should use keyframe2, etc. I can submit the PR if you agree.

🙏 2

So, yes, where there are out-of-date things, it would be great to update them. The renderer, IMO, has become less and less important. A lot of emphasis was given to it early on because there seemed like some good optimizations could be had there. The ident-optimized render came from ideas in Om Next, but under real-world measurement the actual performance wasn't much different than just letting React BE the optimization (due to the overhead of figuring out what to render, etc.), and it created a ton of confusion when you didn't use it right. It was way more of a pain than it was a gain. The multi-root renderer came about to solve the real problem of having a subtree that didn't have to be pre-connected to the root tree, which is a problem better solved (now) by react hooks, which didn't exist when it was created. So, barring React hooks, the MRR is what you'd want (keyframe2 is just keyframe that supports manually-targeted refresh). Fulcro has always been able to do refreshes of targeted components in a sub-tree (and you can take advantage of that independent of the rendering optimization by just using comp/refresh-component!). The !! versions of mutation-related things use that as part of rendering just a local sub-tree. Hooks made it possible to do disconnected data sub-trees without the need for a special optimized renderer. Thus, the multiple-roots renderer, while technically not deprecated (it still works just fine) is simply not necessary because of hooks. Keyframe2 is nice if you need some control over limiting the bounds of a refresh (manually) during a transaction, so rather than encourage people to use MRR, it seems like keyframe2 with hooks and proper use of other optimizations (mentioned above) are more than sufficient. I've never actually succeeded in optimizing a UI using the optimized render setting, even though I've tried all sorts of different approaches. So, my final take on what the developer guide should say is more of the above: That messing with that setting probably isn't going to get you much, and you should instead pay more attention to hooks/use-component, refresh-component!, shouldComponentUpdate, hooks memoization, and possibly keyframe2's only-refresh support.


Should also probably update the docstring in the renderer nses to reflect all of that 😄


MRR still says it is alpha 😛


I haven't forgotten about this, just got sidetracked. I will get back to it.

Mark Wardle14:01:49

Dear all, Happy New Year. I am trying to combine path based routing with nested routing. I’m getting a useful error message : core.cljc:186 2022-01-03T14:15:31.540Z ERROR [com.fulcrologic.fulcro.routing.dynamic-routing:410] - will-enter for router target pc4.ui.projects/ProjectHome did not return a valid ident. Instead it returned: [:t_project/id nil] See and a useful warning message 2022-01-03T14:15:31.849Z INFO [com.fulcrologic.fulcro.algorithms.indexing:103] - component pc4.ui.projects/ProjectHome's ident ([:t_project/id nil]) has a nil` second element. This warning can be safely ignored if that is intended. Props were nil` - but despite my best efforts, I haven’t been able to fix. I feel I should be able to see what the issue is here, but it is eluding me! My structure is Root -> MainRouter -> ProjectPage2 -> ProjectRouter -> ProjectHome. ProjectHome isn’t getting the right data passed in -

(defsc ProjectHome
  [this {:t_project/keys [id active? title date_from date_to virtual long_description
                          inclusion_criteria exclusion_criteria type
                          count_registered_patients count_discharged_episodes]
         :as             project}]
  {:ident         :t_project/id
   :query         [:t_project/id :t_project/active? :t_project/title :t_project/date_from :t_project/date_to :t_project/type
                   :t_project/virtual :t_project/long_description :t_project/inclusion_criteria :t_project/exclusion_criteria
                   :t_project/count_registered_patients :t_project/count_discharged_episodes]
   :route-segment ["home"]
   :initial-state {}
   :will-enter    (fn [app {project-id :t_project/id}]
                    (dr/route-immediate [:t_project/id project-id]))})
I thought I could fix the issue by running delayed routing and pre-merge into the parent component (ProjectPage2) - but I’m obviously doing something wrong here.
(defsc ProjectPage2 [this {:t_project/keys [id name title long_description router] :as project}]
  {:query         [:t_project/id :t_project/name :t_project/title :t_project/long_description
                   {:t_project/router (comp/get-query ProjectRouter)}]
   :ident         :t_project/id
   :route-segment ["project" :t_project/id]
   :initial-state (fn [_] {:t_project/router (comp/get-initial-state ProjectRouter)})
   :pre-merge     (fn [{:keys [data-tree state-map]}]
                    (log/warn "pre-merge : " {:data-tree data-tree :state-map state-map})
                    (merge (comp/get-initial-state ProjectPage2)
                           {:t_project/router (get-in state-map (comp/get-ident ProjectRouter {}))}
   :will-enter    (fn [app {:t_project/keys [id] :as route-params}]
                    (log/info "Will enter project with route params " route-params)
                    (let [id (if (string? id) (js/parseInt id) id)] ;; be sure to convert strings to int for this case
                      (dr/route-deferred [:t_project/id id]
                                         #(df/load app [:t_project/id id] ProjectHome
                                                   {:post-mutation        `dr/target-ready
                                                    :post-mutation-params {:target [:t_project/id id]}}))))})
I am routing to a leaf node - ie /project/38/home is there something I’m missing? I’d be grateful for any pointers thank you.

Jakub Holý (HolyJak)15:01:17

I would print the route params in both :will-enter methods. Are they as you expect? Surely not b/c of the failure, so what are they? Notice the error has nothing to do with your client DB data - the data used is only data from the route params, which you claim to supply. So perhaps also show your change-route call?

Jakub Holý (HolyJak)15:01:17

Hm, I do not remember how this works in details but if you have the project ID inside ProjectPage2 route params but not in the ProjectHome route params then I would guess that the params only include the params for that particular target - and since P.Home does not declare any placeholders in its route segment, it gets not route params (beware - I am only guessing!)

Jakub Holý (HolyJak)15:01:55

In I use computed props to pass such shared ID from a parent to a (grand)child.

Jakub Holý (HolyJak)15:01:14

Or perhaps, for the routing, you could use the 3-arg version of! and pass the ID inside the extra params in addition to it being in the route

Mark Wardle16:01:51

Thanks @U0522TWDA! Actually I used your billing app for a lot of inspiration for this - I’m currently using pushy with the dr/change-route 2 parameter option.

(defonce history (pushy/pushy
                   (fn [path]
                     (log/info "routing: changing path to " path)
                     (dr/change-route @SPA path))
                   (fn [url]
                     (let [path (url->path url)]
                       (if (routable-path? @SPA path)
ProjectPage2 ’s :will-enter is called correctly with route parameters. ProjectHome’s :will-enter gets an empty map in :will-enter: 2022-01-03T16:47:45.531Z INFO [pc4.ui.projects:172] - Will enter project with route params {:t_project/id "67"} core.cljc:186 2022-01-03T16:47:45.532Z INFO [pc4.ui.projects:53] - will-enter for ProjectHome: {} I guess I could forego html5 routing and just get it working with top-level ‘current-project’ but I quite liked the idea of readable URLs and being able to cope with users hitting back and forward buttons.

Jakub Holý (HolyJak)17:01:40

You could use the dr/ functions to parse the route to get the params map and pass those params as the 3rd option to change route. Maybe then they will appear in the will-enter - but test first by calling change - route manually

Jakub Holý (HolyJak)20:01:52

BTW your code is not logically correct. Remember that normally a component = a data entity. Ignoring the routers, you have ProjectHome as the child of ProjectPage2 but it does not display a child data entity - instead they both display the same entity, a project. So should be applied. Though I am unsure how to combine it with the router.... Then you want these sub-views such as P.Home inside a router. If I understand Tony right, that is not the intended purpose of routers - their main motivation is to ensure you do not load more data then you need for the page. A simpler solution would be a (case path "home" (ui-project-home props), ...) but I understand the desire to keep the state of what is displayed in the URL and using routers is the obvious way to do that. But you could also consider some DIY solution, which might turn out to be simpler. BTW this can also be of interest

Jakub Holý (HolyJak)20:01:46

Thinking more about your code, if it was the way it is then both your targets - the P.Page and P.Home - would issue the same (dr/route-immediate [:t_project/id project-id]) which certainly must be confusing for the routing system. It seems you are trying to do st. it was not designed to handle and you would be better off doing it differently, such as the manual switch.

Mark Wardle20:01:37

Thanks Jakub. I really appreciate your insights here. I actually had it working without routing simply using pathom placeholders as per your blog post, but thought I was better switching to using the dynamic routing functionality. I will switch back and keep the routing simple. In fact, I really want a concept of a current project and current patient, which makes much more sense using top-level entries in the db, rather than shoe-horning into routing, as they can then be used across different components as first-class ‘things’. I can easily do tab-menus manually for nice views. Thank you again.

👍 1
Jakub Holý (HolyJak)21:01:00

I could not resist playing with this, see tl;dr: use the 3rd arg of change-route to pass what sub-view (such as P.Home) to display, inside P.Project, use its will-enter and deferred routing (to ensure only-once semantics) to trigger a mutation that stores the selected sub-view info and triggers the deferred route. Parsing of URL into the change-route call left as an exercise for the reader 🙂

Mark Wardle21:01:29

Thanks Jakub! Just off to bed, so I shall have a close look tomorrow thank you!

🙏 1

I have a component that looks like this

(defsc Dashboard [this {:dashboard/keys [quiz-prompt calculator]}]
  {:route-segment ["dashboard"]
   :query [{:dashboard/calculator (comp/get-query MaxCalculator)}
           {:dashboard/quiz-prompt (comp/get-query QuizPrompt)}]
   :ident (fn [] [:component/id :dashboard])
   :initial-state (fn [_] {:dashboard/calculator {} :dashboard/quiz-prompt {}})
   :will-enter (fn [app _]
                 (dr/route-deferred [:component/id :dashboard]
                                    #(df/load! app
                                               [:component/id :dashboard]
                                               {:post-mutation `dr/target-ready
                                                :post-mutation-params {:target [:component/id :dashboard]}})))}
       (ui-quiz-prompt quiz-prompt)
       (ui-max-calculator calculator)))
for some reason, the queries of the sub components arent returning any data… the sub component looks like this:
(defsc QuizPrompt [this {:keys [current-user readiness-quiz] :root/keys [response]}]
  {:query [{:current-user [{:responses [:response/id :response/total :response/created-at]}]}
           {[:root/response '_] [:response/total]}
           {:readiness-quiz [:quiz/id]}]
   :ident (fn [] [:component/ddd ::quiz-prompt])
   :initial-state {}}
When I query for
{:current-user [{:responses [:response/id :response/total :response/created-at]}]}
in the eql tab, i get the data I want but when its nested under another component, the data doesnt load at all


im wondering if the df/load! needs to be changed? my intuition is saying that since Im using the Dashboard query, that would, by default, load the sub queries


when I move the subcomponent queries up into dashboard, it works fine


but I want the queries to live on the sub components..

Jakub Holý (HolyJak)09:01:20

What does the query sent to the backend and the response look like (in the Transactions or Network tab)? Does (comp/get-query Dashboard) look as you expect? How does it differ from when you move the subcomponent queries up into dashboard? Do you have a correct Pathom resolver that can resolve the query? Apply the


[{[:component/id :dashboard]
      [{:maxes [:max/type :max/amount :max/id :max/created-at]}]}]}
        [:response/id :response/total :response/created-at]}]}
     {[:root/response _] [:response/total]}
     {:readiness-quiz [:quiz/id]}]}]} 
this is what the query looks like, I get this as the response
{[:component/id :dashboard]
 {:dashboard/quiz-prompt {}, :dashboard/calculator {}},
 :com.wsscode.pathom.core/errors :com.wsscode.pathom.core/not-found}


but when I strip the query down to just the :current-user portion, it returns data


I guess Im not understanding why the joins on the fulcro side cause the pathom resolvers to fail… :current-user is a global resolver

Jakub Holý (HolyJak)22:01:04

You should not have the component ident in the backend query b/c pathom knows nothing about that.

Jakub Holý (HolyJak)22:01:21

So if seems what you actually want is (df/load! app :current-user SomeComponent) where SomeComponent has the correct query, namely [{:maxes..}..]. So change your current query to match that - see for tips And use targeting to put /link the data to the Dashboard component. From what I see, the Dashboard component actually represents / displays the current-user and should does query for the user 's attributes. You want to split the query into subcomponents so use the :>/some-name trick


is the placeholder reader included in the parallel parser by default?

👎 1

doesnt seem like its working out of the box


figured it out I think! I can pr the placeholder reader stuff into the template if you’d like

🙏 1

but now my fulcro app isnt rendering 😂

Jakub Holý (HolyJak)14:01:36

Great! Yes, pls send the PR!