fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
JAtkins 2021-01-07T01:40:07.159600Z

Dunno if this would come in useful, but I just spent a bit of time to get a rough translator going from fulcro rad attributes => malli-schema format. It will also figure out what entities you have (based of of ao/identities ) and generate the joins required. It has lots of rough edges, but it's good enough for my use-case of dogfooding some random initial state when I need something. I didn't use spec because it's nearly impossible to generate spec1 at runtime. A better solution might be to override defattr to create a spec definition at compiletime, but I only thought of that after I wrote this 🙂. https://gist.github.com/JJ-Atkinson/796d8e124edf684def2283dd9759515d

đŸ€˜ 2
Timofey Sitnikov 2021-01-07T11:52:24.164700Z

Trying to get the https://github.com/fulcrologic/fulcro-rad-demo to add an Account in SQL configuration. Everything works fine till I try to add an account, then I get the error

originalMessage: "Data conversion error converting \"OTHER to ENUM\""
Attached is the complete error listing. What would be a good way to fix this? I think that the issue is in the https://github.com/fulcrologic/fulcro-rad-sql/blob/develop/src/main/com/fulcrologic/rad/database_adapters/sql/resolvers.clj. It may be possible to wrap enum into as-other as can be seen in the https://cljdoc.org/d/seancorfield/next.jdbc/1.1.613/doc/getting-started/tips-tricks#working-with-enumerated-types. Looking for advice on how to fix this.

Timofey Sitnikov 2021-01-08T10:41:16.239600Z

@tony.kay understood, I just wanted to throw the question out there, in case someone would recommend a good solution that I can start with.

Timofey Sitnikov 2021-01-08T10:49:26.239800Z

@tony.kay also sorry about the bad pull request. I started the pull request right after i committed just the addition to the require block, I thought that github would snapshot the commit, but then I pushed two more commits. After that I pressed complete pull request button and the two later commits ended up being part of it. I feel bad about it because I do not like to waste peoples time going through unnecessary code.

tony.kay 2021-01-08T17:45:35.252600Z

no worries. The solution could be “find standard SQL that works for all dbs and use that in the plugin” or “write a protocol for the diffs, and use that to access the dbs from the adapter”. Requires some R&D

Björn Ebbinghaus 2021-01-07T13:36:01.168700Z

@tony.kay What is this line in fulcro.components for?

#?(:clj
   (defn -legal-keys
     "PRIVATE. Find the legal keys in a query. NOTE: This is at compile time, so the get-query calls are still embedded (thus cannot
     use the AST)"
     [query]
     (letfn [(keeper [ele]
               (cond
                 (list? ele) (recur (first ele))      <- THIS ONE ================================================================================
                 (keyword? ele) ele
                 (is-link? ele) (first ele)
                 (and (map? ele) (keyword? (ffirst ele))) (ffirst ele)
                 (and (map? ele) (is-link? (ffirst ele))) (first (ffirst ele))
                 :else nil))]
       (set (keep keeper query)))))
https://github.com/fulcrologic/fulcro/blob/ee9545df41795d1acd5365c1926036740a5da98e/src/main/com/fulcrologic/fulcro/components.cljc#L1311 I am trying to figure out, if/how I can use parameters directly in queries. Like: {:query [:comment/author (:comment/body {:truncated? true})]} I was hoping that when a query is made to the client-side database, the parameter will simply be ignored, but the query will still be sent to the backend unchanged.

tony.kay 2021-01-07T13:49:01.169100Z

The SQL plugin is very alpha. Enumerations, in particular, are known not to work well. I’m actively interested in finding a maintainer, and currently don’t have time to work on it.

tony.kay 2021-01-07T13:51:40.171300Z

It’s used to generate compile-time errors around defsc to prevent common errors. Parameters don’t make sense in static component queries, and the generalized load support allows you to add them when you need them. This makes the use of dynamic queries that add them also largely irrelevant. Fulcro could support that, but I have no intention of doing so at the moment. The earlier versions technically did support this, but no one seemed to use it, including me (and not a single complaint from removal).

tony.kay 2021-01-07T13:54:04.173400Z

It was a source of complexity that I chose not to move forward with, given the amount of complexity it added to everything. I judged it “incidental”. I’m not dead-set on that judgement “forever”, but until I see really compelling use-cases that cannot be solved (reasonably well) with the existing APIs I have no interest in making things more complex based on the occasional “preference” to embed them that way.

tony.kay 2021-01-07T13:55:23.174700Z

My recommendation is to use namespaced keys in the params argument of load, move those into your server parsing env (so they propagate to children). The namespacing allows for a form of “targeting” of the query params. Granted that solution is not perfect, but in the real world with the real features of load, it has solved every case I’ve needed very easily.

💯 1
tony.kay 2021-01-07T14:02:46.175300Z

Sorry, you asked about the specific line. Yes, that line is there to deal with the fact that one could technically embed parameters in queries.

tony.kay 2021-01-07T14:03:11.175600Z

and the error checking doesn’t care about them

tony.kay 2021-01-07T14:04:17.176800Z

When I talk about the query parameter stuff above, I mean that you used to be able to do this: [(:prop {:start ?start})] and then later call set-query to bind ?start to something. That’s the parameter support I dropped.

tony.kay 2021-01-07T14:05:26.178Z

you can still call set-query and put parameters in the query of a component. It could technically have some bugs (I do not imagine it does), because no one uses it to my knowledge, but try it if you wish.

Björn Ebbinghaus 2021-01-07T14:12:31.178200Z

I am trying to. When writing (:comment/body {:truncated? true}), like above, I just get an Invalid expression error from cljs. When I quote the expression, I get was destructured in props, but does not appear in the :query I am trying to figure out how to fix this for me.

Björn Ebbinghaus 2021-01-07T14:14:47.178400Z

It doesn’t matter so much whether it works or not. It’s not really worth my time right now, but curiosity grabbed me.

Björn Ebbinghaus 2021-01-07T14:16:02.178600Z

I saw the specific line and though: “Great, I am not the first one to try this” 🙂

Björn Ebbinghaus 2021-01-07T14:52:52.196100Z

@tony.kay While I am already at it: Here are two questions that I thought about and would love your opinion whether these things make sense or not. I am aware that these things are just “sugar” and not a necessity. 1. How would I write a function for something like an 
 “anonymous component” for query purposes? I often have to write components just for the query:

(defsc Child [_ _] 
 {:query [:id]
  :ident :id})

(defsc Something [_ {:keys [children]}]
 {:query [{:children (comp/get-query Child)}]}
 (str "I have " (count children) " children!")
)

; to

(defsc Something [_ {:keys [children]}]
 {:query [{:children (comp/sc [:id] {:ident :id})]}     ; <- something like this   ; or (comp/sc {:query [:id] :ident :id})
 (str "I have " (count children) " children!")
)
2. Would is make sense to allow idents of length one for singletons?
(defsc NavDrawer [_ _]
 {:query [:open?]
  :ident (fn [] [:component :nav-drawer])

(defsc Root [_ _]
 {:query [{:nav-drawer (comp/get-query NavDrawer)}]}) 

; The db:
{:nav-drawer [:component :nav-drawer]   ; This seems redundant for singletons
 :component {:nav-drawer {:open? false}}

;;; instead NavDrawer with a single item ident
(defsc NavDrawer [_ _]
 {:query [:open?]
  :ident (fn [] [:nav-drawer]))

; would lead to this db:
{:nav-drawer {:open? false}}

tony.kay 2021-01-07T15:04:41.202500Z

1. Technically you just need to add the metadata that get-query adds, and have the :component in that have a component options ident (which in cljs is just an js object with some special fields). The only thing special about it is the component used for normalization; however, I do not see why you’d want that, because you should be doing the rendering in the child, and therefore should be writing a child anyway. Writing a separate component just for a query is a very rare thing indeed (your example is a contrived use-case). 2. you can certainly write a macro that transforms that, but I don’t want to add yet another syntax sugar onto a macro that already has so many little things like that. On the one hand it’s “par for the course”, but on the other what “table name” am I to auto-select for you? Also, since keywords without a vector mean :x => [:x (:x props)] having another special variant [:x] => [:component/id :x] along with [:x :y] => [:x (:y props)] or (fn [] literal-ident) is adding to a thing that is already a bit excessive, and doesn’t even follow the “sugar pattern” of pulling something from props to generate the ident.

tony.kay 2021-01-07T15:06:49.203500Z

A more solid use-case for (1) is (df/load! :kw (sc ident query)) where you want normalization of some data into the client that doesn’t have UI of that shpe

tony.kay 2021-01-07T15:09:34.204600Z

See configure-component! for ideas on making the query thing, but be careful: the component registry is normally part of the story, and your use-case probably doesn’t want to register, so you don’t really want all the mess that configure component does.

Björn Ebbinghaus 2021-01-07T15:21:02.206Z

To 2.: Is it really another case? For singletons a table-name doesn’t make sense. I thought not about a keyword without a vector, but a vector with a single entry as a literal-ident (fn [] [:x]) Like you could for a normal map in clojure: (get-in state [:nav-drawer]) and (assoc-in state [:nav-drawer])

tony.kay 2021-01-07T15:23:02.206600Z

To test if your thing works, just make sure get-query and get-ident (both arities) work on it.

tony.kay 2021-01-07T15:26:25.206900Z

I see what you’re getting at. I definitely do not want to introduce that complication. It would affect a lot of things for no tangible benefit.

tony.kay 2021-01-07T15:26:55.207100Z

(state would then have to be able to contain card 1 vectors as idents, which would confuse all manner of things)

tony.kay 2021-01-07T15:28:20.207300Z

right, I don’t have tests covering the case for query params in component static queries but there is some legacy bit here and there that made it through the removal. Given the bug, then only way to do it right now is via set-query!

tony.kay 2021-01-07T15:28:44.207500Z

which you actually need to use anyway, since parameters (by their nature) vary

tony.kay 2021-01-07T15:29:18.207700Z

My general reasoning was: You need to pre-set bindings and parameters before they make sense, so why not just use set-query?

tony.kay 2021-01-07T15:29:42.207900Z

but because all that introduces the need for logic in the same code points as load, why not just use load params?

tony.kay 2021-01-07T15:30:37.208100Z

The answer, of course, being you want the params in a particular spot. My workaround mentioned before works quite well for me, but you still have set-query! if you really really need it.

exit2 2021-01-07T16:32:36.209Z

Is it possible to watch changes in props of a child component? I want a parent component to be able to see if a child (form) component is valid (I’m using the non-spec validators)

exit2 2021-01-07T16:33:14.209500Z

Oops didn’t mean to pin that 😄

tony.kay 2021-01-07T16:33:17.209700Z

the props of the child flow through the parent

tony.kay 2021-01-07T16:33:20.209900Z

yeah, I figured 🙂

tony.kay 2021-01-07T16:34:26.211300Z

The trick is that if you use an optimized call the re-renders just the child, then you need to make sure the parent is refreshed (usually by using something like :only-refresh. Depends on renderer. Oh, in F2, which I think you’re using, you’d include a parent prop in the follow-on read

exit2 2021-01-07T16:35:05.212100Z

ah I see.. so on any updates to the child just make sure those transactions are also updating the parent ident

tony.kay 2021-01-07T16:35:08.212300Z

(transact! this [(update-field 
) :parent-prop])

👍 1
tony.kay 2021-01-07T16:35:13.212500Z

yep

exit2 2021-01-07T16:35:16.212800Z

got it, thanks

tony.kay 2021-01-07T16:36:31.214Z

in F3 I figured out that the targeted refresh is usally not needed for performance (if you do other things right), so default is to render from root, which in turn makes doing that a non-issue
you don’t even have to think about it. It pushes the potential complexity off to an optimization step instead of troubling you with it from the outset as a pre-optimization

exit2 2021-01-07T17:37:33.216100Z

Speaking of validation, on initial load my fields are “unchecked” but once I edit one of them, the rest of them turn invalid. Is there a way around this? I’d rather not show validation messages until the user has tried to modify that field

exit2 2021-01-07T18:17:56.217600Z

I guess I just don’t see why editing one field would make the rest invalid

tony.kay 2021-01-07T18:17:56.217700Z

“edit one of them” is never and has never been the trigger

tony.kay 2021-01-07T18:18:06.218100Z

mark-complete is what changes that

tony.kay 2021-01-07T18:18:16.218400Z

and it can be called for a field or the whole form, but it is in your control

exit2 2021-01-07T18:19:00.219Z

I see.. so what is the recommendation in this scenario? The onChange is causing the whole form to be invalid at this point

tony.kay 2021-01-07T18:28:22.220200Z

add the field argument to mark complete

Björn Ebbinghaus 2021-01-07T20:10:18.224900Z

Hm. I thought that could be as simple as remove any checks if an ident is exactly card. 2. Ok. Thanks for your reply.

Björn Ebbinghaus 2021-01-07T20:56:57.226500Z

Does anyone has an example with two nested dynamic-routers, where both routers have a parameter in their route-segment? I am stuck with initializing the state correctly.

tony.kay 2021-01-07T21:53:21.226700Z

what are your route segments?

Björn Ebbinghaus 2021-01-10T01:12:48.266200Z

@tony.kay Hm. Now I have noticed that I will often have the case that the children have the same ident as their parents. 😕 Do you have a pointer how to avoid this messing with my routing? Or nested routers in general? I have to admit that I’m pretty lost in figuring out how to do it “right”.

tony.kay 2021-01-10T02:07:45.266400Z

What are you doing that is causing your children to have the same idents as the parent?

tony.kay 2021-01-10T02:08:13.266600Z

especially as routes? It seems like in a path once you’ve identified a thing, that the children of that thing would not be the thing

Björn Ebbinghaus 2021-01-10T11:37:17.275500Z

Ok. I stripped down my case to the following code. I think writing it down like this is easier than me trying to explain it to you.

(defsc ProposalList [_ _]
 {:query [::process/slug {::process/proposals (comp/get-query Proposal)}]
  :ident ::process/slug
  :route-segment ["proposals"]}
 (button {:onClick open-add-proposal-modal!} "Add a new proposal to this list!"))

(defsc ProcessHome [_ _]
 {:query [::process/slug ::process/title ::process/description]
  :ident ::process/slug
  :route-segment ["home"]}
 (button {:onClick open-add-proposal-modal!} "Start with a new proposal!"))

(defrouter ProcessRouter [_ _]
 {:router-targets [ProcessHome ProposalList]})
(def ui-process-router (comp/factory ProcessRouter))

(defsc Process [_ {:ui/keys [process-router new-proposal-modal]}]
 {:query [::process/slug 
          {:ui/process-router (comp/get-query ProcessRouter)}
          {:ui/new-proposal-modal (comp/get-query ProposalModal)}]
  :ident ::process/slug
  :route-segment ["decision" ::process/slug]}
 (comp/fragment
   (dom/a {:href (str "/decision/" slug "/home")} "Back to home!")
   (ui-global-add-proposal-to-process-modal new-proposal-modal)
   (ui-process-router process-router)))
I change-route! based on the URL, when it changes. I extract all params from the route-segments and add them as extra-params to change-route! . For example: The site navigates to /decision/my-test-decision/proposals , I call (dr/change-route! app ["decision" "my-test-decision" "proposals"] {::process/slug "my-test-decision"}) . This way the router targets in ProcessRouter get the slug as well.

tony.kay 2021-01-10T17:54:36.280600Z

Process is not a process. Use the info out of the child to know your slug.

tony.kay 2021-01-10T17:55:02.280800Z

I would call is something different
just because you need to know something about your children does not mean you are your children

Björn Ebbinghaus 2021-01-11T10:31:58.286700Z

What exactly do you mean with “Use the info out of the child to know your slug.“? Notice, that I shortened the query for Process and it could query for ::process/title as well. In my case even the ui-global-add-proposal-to-process-modal has the ident: ::process/slug because it needs access to ::process/all-proposals (As a proposal can have another proposal as its parent) and ::process/slug

tony.kay 2021-01-11T17:23:40.291800Z

Your choices are: ‱ Reorganize things so you don’t end up with deferred routers on the same ident. ‱ Don’t use dynamic routers.

tony.kay 2021-01-11T17:24:18.292Z

“has the ident: `::process/slug` because it needs access to `::process/all-proposals` ” is an invented constraint by you not Fulcro or Pathom

tony.kay 2021-01-11T17:25:39.292200Z

“I shortened the query for `Process`  and it could query for `::process/title` ” But IS it a Process? It has to, as a parent, know about children. composition recommendations are that children are unaware of parents. In a UI parents are very very often forced to have knowledge of their children. You mix it into the query and render body. You’re stuck with it.

tony.kay 2021-01-11T17:26:20.292400Z

don’t confuse the concerns of the URL bar with the data model of your UI with the data model in your back-end.

Björn Ebbinghaus 2021-01-12T00:34:09.301900Z

When you say: > It has to [..] know about children and > parents are very very often forced to have knowledge of their children Do you mean that in a sense, that they are just aware of their existence and/or relationship? Or do you mean that in a practical sense that they know about the query and props a child has? --------------------------------------------- So I renamed the component to ProcessContext and pulled out the Process related stuff into a new component ProcessOutline and changed the ident of the component. Is that what you had in mind?

(defsc ProcessOutline [_ _]
 {:query [::process/slug ::process/title ::process/description]
  :ident ::process/slug}
 (dom/a {:href (str "/decision/" slug "/home")} "Back to home!"))
(def ui-process-outline (comp/factory ProcessOutline)

(defsc ProcessContext [_ {:ui/keys [process-router new-proposal-modal] :keys [process-outline]}]
 {:query [::process/slug 
          {:process-outline (comp/get-query ProcessOutline)}
          {:ui/process-router (comp/get-query ProcessRouter)}
          {:ui/new-proposal-modal (comp/get-query ProposalModal)}]
  :ident [:process-context ::process/slug]
  :route-segment ["decision" ::process/slug]
  :will-enter (fn [app {slug ::process/slug}]
                (let [ident (comp/get-ident ProcessContext {::process/slug slug})]
                  (dr/route-deferred ident
                    (fn []
                      (df/load! app [::process/slug slug] ProcessOutline
                        {:target (targeting/replace-at (conj ident :process-outline))})
                      (mrg/merge-component! app ProcessContext (comp/get-initial-state ProcessContext {:slug slug}))
                      (dr/target-ready! app ident)))))}
 (comp/fragment
   (ui-process-outline process-outline)
   (ui-global-add-proposal-to-process-modal new-proposal-modal)
   (ui-process-router process-router)))

tony.kay 2021-01-12T01:51:19.302300Z

It’s your program, I’m not sure what you’re thinking. I was just expressing that it makes little sense to me for the identity of a specific parent to be identical to a specific child of that same parent. “I am my own grandpa” is not a good structure. If you have a desire to split duties of rendering that can be done with functions. If the parent is being given the slug because you want to show it in the parent, why not just pull the slug out of the child props?

tony.kay 2021-01-12T02:56:35.303Z

Your idea of using an alt table is sorta “customized denormalized normalization”


Björn Ebbinghaus 2021-01-12T14:35:57.305700Z

Hm
 I thought using the props of a child was kind of bad, because it is misusing the colocation aspect. What happens, when the query of your child changes? That would break the parent. That’s the motivation behind my recent question about an “anonymous component”. > Your idea of using an alt table is sorta “customized denormalized normalization”
 How would I compose it otherwise? I don’t want to load! :process-context as it isn’t the concern of the backend to know about this. I have to do it manual, right?

Björn Ebbinghaus 2021-01-12T15:26:05.306400Z

Ok, no, not using the props of children is stupid... You need to work with them when you do any kind of filtering or sorting, or similar. 😕

tony.kay 2021-01-12T17:25:26.308500Z

Yep. Parents always need to know something about their children. It is the reverse that is more of a problem, because the parent context is usually a mystery, so to get things from a parent we always provide things like callbacks and published API of the child so the parent can “black box” discover it. It’s couping, but it’s one-way and well-defined. The parent also needs to know the query of the child (it has to compose it in). That is a grey area because you are right, it doesn’t need to know the content of the query
unless it is a TodoList where “check all” state comes from looking at the children, or your case, where you want to know the slug. It is just the way it is. You don’t want to define a new prop or query to track these kinds of things. The practical cost of the parent knowing something about the child, as you’re realizing, is trivial, worth it, and standard React (you look at the child API to find callbacks, why not consider the query a part of the “published API”).

Björn Ebbinghaus 2021-01-07T21:59:17.230900Z

[„decision“ :process/slug] And [„proposal“ :proposal/id] or even [„proposals“] Thinking about it. I have problem placing another dynamic router in a target with a parameter. The second parameter isn‘t even needed.

tony.kay 2021-01-07T22:00:52.233100Z

should nest fine, but you need to make sure you pass a complete route to it
i.e. the vector of a route MUST have 4 elements in order to match

tony.kay 2021-01-07T22:01:17.233700Z

if you fail to specify a bit of the segment the routing will stop at that point and assume the sub-routes are “right” already

Björn Ebbinghaus 2021-01-07T22:01:32.234Z

But how do I initialise the inner router?

Björn Ebbinghaus 2021-01-07T22:02:23.235300Z

It can‘t be initialised on app startup because of the parameter.

tony.kay 2021-01-07T22:46:34.235700Z

sure it can, you can initialize state and set root with set-root!, then use dr/route-to!

tony.kay 2021-01-07T22:46:40.235900Z

then mount w/o initialize

tony.kay 2021-01-07T22:47:27.236100Z

I use a “ready” flag on the root node to prevent rendering until things have settled (i.e. a ok-result handler on loading app config or something)