Fork me on GitHub

So, I realize I’ve been pushing code on the main brach of the RAD demo that won’t work with the current deps (I work against source)…I’m going to remember to use branches 🙂 In the meantime I’ve updated the main branch and released rad/rad-datomic to versions that “work”. I’m in the middle of file upload refinement, so that part has bugs right now, but the demo should work again, assuming you use the Datomic adapter (SQL isn’t done yet).


This version has a lot of refinements around extensibility: • Changed how save works. It now uses save middleware. • Changed how delete works. It now also uses middleware. • Made form layout more customizable


I also refined the parser setup around adapters just providing plugins.


The book needs updates, but should not be terribly far off. Very little of the UI code changed (if at all). It’s mostly setup refinements, and the fact that I’ve got most of file upload support working.


Is it possible to have mutually recursive components? I.e., I’d like to have a list of item s, where an item can optionally have a list (with further items). Specifically, (declare List) seems to be a no-no when trying to (get-query List) from Item .


I’m not sure if it’s possible for two different components (query engine hits maximum call stack for me), but you might go with recursive queries


Making item contain item s works fine indeed. It would be handy to have a list abstraction between them though, as I this would be a meaningful concept in this particular context. I.e., the same list of items might show up twice in different places, and I’m not sure how to do that (easily) unless I make list s concrete thing. However, if that’s not an option, I’ll have to work around it somehow.


If you don’t have any additional data associated with the list entity itself (so that you need it only for rendering purpose), then you might go with something like

(declare ItemList)

(defsc Item [this {:item/keys [label more]}]
  {:query         (fn [] [:item/id :item/label {:item/more '...}])
   :ident         :item/id
   :initial-state (fn [{:keys [id label more]}]
                    {:item/id    id
                     :item/label label
                     :item/more  (mapv #(comp/get-initial-state Item %) more)})}
    (div label)
    (div {:style {:paddingLeft "16px"}}
      ((comp/factory ItemList) {:items more}))))

(def ui-item (comp/factory Item {:keyfn :item/id}))

(defsc ItemList [this {:keys [items]}]
    (for [item items]
      (ui-item item))))

(def ui-item-list (comp/factory ItemList))

(defsc Root [this {:root/keys [items]}]
  {:query         [{:root/items (get-query Item)}]
   :initial-state {:root/items [{:id    1
                                 :label "A"
                                 :more  [{:id    2
                                          :label "B"}
                                         {:id    3
                                          :label "C"}]}
                                {:id    4
                                 :label "X"
                                 :more  [{:id    5
                                          :label "Y"
                                          :more [{:id    6
                                                  :label "Z"}]}]}]}}
  (ui-item-list {:items items}))


Alas, I would need the lists to have idents so that I could reference them. Thanks for the idea, though!


A potential solution that came to mind, that feels very, very dirty, is to essentially embed the behaviour of both lists and items in the item, and switch between them conditionally.


Yep, I’ve also just thought about something like that, e.g. using union query on the item. But that adds quite a bit of complexity…


Yeah, certainly not ideal!


Mutually recursive components are at odds, as you’ve found, with dynamic queries (the query is evaluated at runtime by asking components for their queries). It is probably true that there is some implementation of get-query for which this could work, but it would be a dramatic expansion of how that system works. So, the short answer is “no”.


I think there might be a way of leveraging union queries with recursion…but I’d have to code an example to get it clear in my own head. another option is to consider that you could choose an arbitrary “max depth” and code components like List1, List2, List3 that use the same function for render (and other commonality like the ident), so that get-query is never a recursive call chain. It’s a bit more code (not a terrible lot), and doesn’t really affect how you implement the data model at all. It works with load, though it does send a “longer” query than gets you…but it is also easy to understand. A macro can also be created that churns out such a thing with a single declaration if you prefer less visible code.


A set number of recursion levels wouldn’t really work in this case, unfortunately. Alright, too bad, but good to know that I shouldn’t head down that road. It seems direct recursion is what comes closest to the structure that I want to enable.

Jakub Holý (HolyJak)09:02:28

Hello! I have routing connected to the URL and I want to do: read the URL -> extract the route -> find the target to check its options -> optionally modify the route -> dr/change-route. This is the function I use to find the target and read its options and my question is whether that is indeed the correct/optimal way to do so:

(defn default-subroute [app root-comp route]
  "Given a new route, find the target component and check its options for a
  `:default-subroute` to add to the route."
  (-> (dr/proposed-new-path app root-comp route)
      (comp/component-options :default-subroute)))
Background: I have two level of routers, something like /person/123/view/relatives where I can vary the ID of the person to display and what view at the person's data to display. When I navigate to /person/123 I want to show the "default" view but that does not work out of the box: when I change the URL from /person/123/view/relatives to /person/456/ , the new route contains no information for the sub-router and those it will stay as-is, showing still the "relatives" view. So my idea is to add :default-subroute ["view" "default"]to the component with the person id router, extract it in the URL->route code and, if present, append it to the route so that the URL /person/456/ will actually route to /person/456/view/default . Thank you!


I don’t see any immediate problems. The general philosophy is you can throw whatever you want in the component options and grab them for your own purposes at any time. The wise-ness of a particular routing solution is usually revealed by its runtime behavior over time.

Jakub Holý (HolyJak)10:02:30

Another question / bug report / feature request: I have the pure react component Paragraph that expects a string as a child. If I explicitly pass in nil/empty props, it works just fine:

(ui-paragraph nil "hello world!")
but if I omit the props then something tries to interpret the string as props, leading to these errors:
(ui-paragraph "I am no props!")
;; =>
;; backend.js:6 Warning: Invalid attribute name: `0` in p (created by require)
;; ...
;; backend.js:6 Warning: Invalid attribute name: `11` in p (created by require)
I understand that there is code in Fulcro that checks at runtime whether the thing passed in is props or a child (and it is recommended to pass in props explicitly if you run into performance issues). I guess it should get a little smarter to understand that a string most certainly isn't a props map? (I see now that the docstring of react-factory reads The returned function will accept CLJS maps as props (not optional) so it is perhaps just my fault having assumed that the factory function from react-factory behaves the same as the dom elements.)


Right…copy react-factory and customize it. If you want complete interop, look at macro-create-element in dom.cljs. You’d need to write a wrapper for it, but it is what the regular DOM macros emit calls to when the macro cannot fully expand at compiler time (thus the name).

👍 4

I found (and fixed) a but in mutations joins today. Tempid rewrite was out of order (too late) and the query used for mark-sweep was being misgenerated leading to weird merge behaviors (things like form config disappearing on merge). On clojars as 3.1.15-SNAPSHOT, will do formal release soon.

👍 8
🎉 4