Fork me on GitHub

New hooks helpers coming. A long-time pain point for Fulcro users is mounting components that are transient (like dropdowns) where you want to use real state (so loads work), but you don’t want to have to generate the query joins, manage initial state, etc. This is especially painful when you have a lifecycle that needs a lot of dynamics. This new hooks support stuff makes those use-cases very nice. Check out this short demo.


This is totally our biggest painpoint, autocompletes which have a lot of state. So right on point as usual.


This is available on the RC I just pushed to clojars: 3.1.2-RC5, which also contains a few bug fixes, one important one around transact!! which affects mutation helpers.


NOTE: That hooks demo requires the floating root renderer to work, which I’m probably just going to change to be the default…it’s essentially keyframe2 with multi-root support, which is proving to be the most useful and tuneable for performance.

Jakub Holý (HolyJak)14:04:46

Hi @tony.kay! I experienced surprising behavior with the new support for route params. I have 3 levels of routers (+ components):

  OrgRouter ;; route: [org :org-id]
       Empty DetailsReport1
Now when I
;; setup
(routing/route-to! APP Empty {:org-id "org1"})
;; test:
(routing/route-to! APP DetailsReport1 {:org-id "org1", :report-param1 "dummy"})
the second routing I expect to only have an effect on the DetailsRouter and DetailsReport1 but all three router UISMs receive route! with the new route param even though they root cares about none and OrgRouter only cares about org-id, which hasn't changed. That is a problem because OrgRouter uses deferred routing and issues a df/load! - and thus unnecessarily reloads its data and delays everything. Am I doing something wrong? Thank you!


@holyjak will-enter ? yes, it is side-effect free and is called as part of the routing layer…that is normal

Jakub Holý (HolyJak)16:04:36

Well, it returns route-deferred with a load! that is then executed so it isn't free of side effects


Just curious, what happened to query->ast and ast->query? It’s mentioned in the book, but I’m not sure where to find it… Currently trying to work out how to turn my nested structures into sql queries… It doesn’t appear in the com.fulcrologic.fulcro.components namespace, unless there’s a different way to do this?


oh @holyjak I might have introduced a bug with recent changes…you on latest RAD w/latest Fulcro?

Jakub Holý (HolyJak)17:04:38

Previous RAD master, before you added sorting etc


I’m on other things today…feel free to look through recent changes to Fulcro in that ns and see if you can identify where I introduced the problem…I added that new :can-change-route thing recently

👍 4
Jakub Holý (HolyJak)21:04:31

I will try to find time for it


@folcon oops. Those went into the EQL library. (I’ll fix book)


I have not updated that chapter in a while


I should have left aliases to them, though


Wilker (mostly) and I created that separate library to standardize the query language of Fulcro/Pathom


we use a lot of the same code for the two libraries, and expect other ppl to want to support it as well, possibly outside of the confines of Fulcro or Pathom


Is there somewhere that I can look where I can get some understanding on how to map this to the space between resolvers / jdbc-sql? It’s not the end of the world if I have to hand write the queries, but if that’s not necessary that would be nice…


@folcon RAD’s SQL adapter


the SQL adapter isn’t fully functional, but it can already write most id-based queries, and save most forms


and if you read the code, you’ll see that much power really didn’t take that much code 😄


Pathom is really your friend here. All you have to do is resolve one layer at a time, and it will build the tree


On save, Fulcro’s normalized state and form-state are the powerhouses…they let you describe a minimum delta in a very machine-friendly form.


So, understanding how RAD does it will teach you a lot


Hmm, ok. I’m trying to pick things up piecemeal though, so I might stick to handwriting stuff for now… and then come back and look over this… Thanks for the tip =)…


those two functions are the majority of the read resolution


but yeah…start with inefficient: Just resolve things by ID one at a time, that’s easy…then learn how to use batch? in pathom.


do not do complex joins at first in SQL…that is definitely an anti-pattern for starting up (it’s an acceptable optimization later, once you have the graph working)


If there’s a db relation to traverse, write a very simple resolver that can take you from one ID to the other….let Pathom do the will be extra queries at first, but trust that this is an easy thing to optimize later, and you’ll love the flexibility you immediately get (and the lack of complex code).


The temptation is to start by writing complex SQL to do efficient server interactions…that will lead you down a dark road. Optimize later. This is important (and turns out to be pretty easy).


How do I deal with the foreign key id mismatch then? For example, pathom knows about these:

:account {:account/id 1 :account/email ""}
:team {:team/id 1 :team/account 1 :team/project 1}
:project {:project/id 1 :project/account 1 :project/tasks [1 2 3 4]}
:person {:person/id 1 :assigned/tasks [2]}
[{:task/id 1 :task/account 1 :task/blurb "Some stuff"}
 {:task/id 2 :task/account 1 :task/blurb "Other stuff"}
But the tasks table holds the actual foreign key for project: So stick to a resolver for: :project-by-id which gives it’s :project/id and :project/account, but then has to join to :tasks, to get the list of id’s or at the very least query it? Similarly I should create a resolver for :assigned/tasks by task-id and person-id? As that will be a joining table? This is completely ignoring the fact that I’d like to also filter by account info =)… So 3 resolvers?


@folcon Start by writing a resolver for each of the 5 entities (account, team, project, person, task) that takes an id and resolves the other attributes. The trick is for foreign key attributes, don’t return the foreign key itself, but rather a map with the id set to the foreign key. So for example your person resolver for the example above would take :person/id, do its query, and return something like:

{:assigned/tasks [{:task/id 2]}
The project resolver would take :project/id and return:
{:project/account {:account/id 1}
 :project/tasks [{:task/id 1} {:task/id 2} {:task/id 3} {:task/id 4}]}
And so on. If you do this, Pathom will take care of the rest and, given any starting node, be able to traverse the entire graph as needed. Once you have that working, then you can optimize with things like batch resolvers, inventing edges to make things easier for the UI, etc. But get the basic graph traversal working first.


Thanks! I’ll give that a go


Hi, I am starting my first fulcro project. I just modified the latest fulcro-template to do an LDAP login. Then I noticed, that all passwords are in the logs. So I am looking for a robust way of hiding sensitive information. Some transactions should be marked as sensitive maybe even on fulcro level. What is the best practice?


which logs @jj974? The inspect/console ones? Production builds won’t have either.


or server-side? In that case, it’s just your own logic. In terms of application state, you may want to use component-local state to prevent passwords from being in the state atom.


(defn log-requests [{:keys [env tx] :as req}]
  (log/debug "Pathom transaction:" (pr-str tx))
yes server side, I don't event want to see my passwords on the screen. I think that this ldap thing is not the best way. I have to change it to oauth.


yeah, just make your own versoin of that function


are fulcro components clojure.specable?


@tony.kay This is my new version, a little bit better, I dont know how to grab :password from tx, it would way to complicated for a logger probably, thanks

(defn log-requests [{:keys [env tx] :as req}]
  (let [o (pr-str tx)]
    (if (clojure.string/includes? o "password")
      (log/debug "Sensitive data in transaction" (subs o 0 10) "...")
      (log/debug "Pathom transaction:" o)))


Should be pretty easy actually. Create a set that lists all the keys you consider sensitive. Then in your logging plugin, before logging the transaction, walk the query and for any key in that set elide its value (set it to “” or “elided” or whatever. Then pass your sanitized transaction to the logger (but be sure to return the original transaction from the plugin).


I will try to do my best, this structures are not so simple as I hoped, First I have to implement core functionality of my app.


@lilactown of course, just write a render function and pass the args to it from the component 🙂

(>defn render-c [this props]
  [comp/component? (s/keys :req [:thing/blah]) => react-element?]

(defsc C [this props]
  (render-c this props))
But I would not recommend it for since specs can be super slow and you’re in the rendering pipeline…but go for it if you want…you could also easily make your own defsc macro and throw your spec into the component options map


(defmacro defSC [sym args options-map & body] 
  (let [props-sym (gensym "props")]
    `(comp/defsc ~sym ~(vector (first args) props-sym) ~options-map
       (let [spec (comp/component-options ~sym :spec)
             ~(second args) ~props-sym]
         (conform! spec ~props-sym)
         [email protected]))))
something like that would prob do it


cool, thanks!



(defSC X [this props]
  {:spec (s/keys ...)
  (div ...))


completely off the cuff, but that should be close


the props juggling is the tricky part, since you might want to say:

(defSC X [this {:keys [v]}]
  {:spec (s/keys ...)
  (div ...))
and unless you generate a symbol for the props you’ll get syntax errors out of it, but you still need to force the destructuring for the body.


macros are the cats meow for this kind of thing 🙂


yeah, that gives me some things to think about. thanks!


re: being slow, I wish there was a magical wand to make code I write faster

Jakub Holý (HolyJak)21:04:20

Why could I be getting this frontend compile error when obviously I do have columns defined?

21 | (report/defsc-report Offboarding
Encountered error when macroexpanding
defsc-report Offboarding is missing or invalid option at line 21 mb/ui/offboarding/ui.cljc
  22 |   {::report/title "Report"
  23 |    ::report/source-attribute :offboarding/all-offboardings
  24 |    ::report/row-pk offboarding/offboarding-id
  25 |    ::report/columns [offboarding/offboarding-id]
[$req_BANG_ invokeStatic "report.cljc" 161]
               [$req_BANG_ invoke "report.cljc" 158]
               [$defsc_report invokeStatic "report.cljc" 202]
               [$defsc_report doInvoke "report.cljc" 183]
due to
(req! &env sym options ::columns #(every? symbol? %))
Which should not fail since I do have
(defattr offboarding-id :offboarding/id :uuid  {:attr/identity?      true})


Hm, not seeing it either @holyjak


you’re missing parameter list


[this props]


macro checks need improvement…they were written very quickly for conj last Nov, and I’ve not made them much better since

Jakub Holý (HolyJak)22:04:24

Now I am getting

react_devtools_backend.js:6 ERROR [] -  No layout function found for form layout style :default 
    in mb.ui.offboarding.ui/Offboarding (created by mb.ui/RootRouter)
I have had it earlier too. I will try full rebuild... I have changed it to (report/defsc-report Offboarding [_ _] {...})


don’t use _ for params


I think I added a warning for forms…but the generated body has to use those syms, and if they are both _ then bad things happen

👍 4

remember that you can start coding a UI in the body of any of those immediately…all the macro does is drop (`render-layout this props)` in for the body if you don’t make one…and if you name both params _ you get (render-layout _ _) which obvs won’t work


The macro could easily patch that, but again, lots to do…