Fork me on GitHub
#fulcro
<
2021-12-11
>
zeitstein10:12:21

I have an attribute in my remote database whose value can either be a string or a reference to another entity in the database. I have no idea how to write a query to handle this in Fulcro. If it's a string, my query should be:

(defsc ...
  {:query [:name :value])
If it's a reference:
(defsc ...
  {:query [:name {:value .}])
Is there a way to write a single composable component which combines these two queries?

xceno12:12:22

Sounds like a job for a custom pathom resolver, or both a resolver and using dynamic queries. I'm on my phone right now so I can't really post any code. You could also invent a new "virtual attribute" and just query for both cases and handle the rest client side

zeitstein13:12:23

Ah, forgot about dynamic queries. That sounds like what I need. Let me consult the developer's guide and report back. Thanks, xceno!

zeitstein14:12:30

I'm having a hard time understanding the Dynamic Queries chapter in FDG. (I bounced off it the first time I tried reading it as well.) So, let me try to describe the situation. Assuming I get back from the server something like:

{...   ...
 :list [{:name "no ref"
         :value "string"}
        {:name "ref"
         :value {:id "some value id"}}]}
I would like to map over the :list array with a ui factory. The thing I'm imagining is to check the type of :value and set the query accordingly. Or use the ui factory with appropriate qualifier?

Jakub Holý (HolyJak)17:12:57

You can also simply query just for :value and either you get a string or a reference. If you want the entity instead of just a reference then I think union queries are the way to go (though I would first think hard about simplifying the data model). And you will likely need to add an attribute that will help distinguish the 2 cases, st like :type :entity

xceno10:12:43

I gave this some more thoughts over the weekend and I think you're setting yourself up for a very hard time if have two completely different results for the same keyword. Everywhere you use this keyword you'll need to rewrite your query to include/exclude the joins. Normalizing this might become a challenge as well, etc. etc. imho you're far better of by splitting your attribute into two, one for each case. Also, what backend do you use? (Don't get me wrong, you can make this work if you really want to, but think about if all this additional work is really worth it)

💯 1
zeitstein17:12:41

Thanks everybody for your suggestions, and @U012ADU90SW for giving it more thought – very kind! Indeed, I ended up "splitting my attribute into two" in my database (Asami), just to make some progress. I have to admit, though, I'm really struggling with Fulcro. Each time I want to add a new bit to my app, it's a struggle. I'm losing my motivation. It took me three days to get to a point with Svelte I'm still not close to reaching after two weeks with Fulcro. Just can't seem to get a solid grip on everything.

Declan19:12:14

I agree that it's a steep learning curve and probably only for advanced users - I don't think I would recommend a new user start with Fulcro. I've been working with it solidly for well over a month and only recently started to feel that things were "joined up" I've built a few React apps and after some time they start to become unwieldy and harder to maintain I think Fulcro is about taking that next step towards very long term, sustainable development. I'm really enjoying clojurescript and I do think this is an investment in time that will pay off.

❤️ 1
thanks3 1
1
Jakub Holý (HolyJak)08:12:11

I am sorry to hear that you struggle! Declan is right that Fulcro has a steep learning curve - but it is worth it. I don't think that the comparison to Svelte is fair. Does Svelte do state management? Data loading? Data deduplication? I don't think so. In this case you are I think running into a weirdness in your data model that is causing you the pain. It is strange that a thing can be either a string or a data entity, don't you think? You should address that in your plumbing code (i.e. Pathom) and expose a simpler data model tuned for the UI - that will make your life much easier. I admit that the world is a weird place and you encounter such strangeness and you have to decide where and how to deal with it. Propagating it to the UI is IMO not optimal. I don't want you to loose faith in Fulcro. If you want, we could have a chat and look at your challenges together. I https://holyjak.cz/holy-dev.html and the first, non-binding, hour is free. I would be happy to use that hour with you, even if you wouldn't hire me afterwards. So give it a thought 🙂

❤️ 1
zeitstein10:12:35

Thanks for the encouragement! And for the offer, Jakub, that's very kind. Sorry about venting my frustration. Clearly, there's no comparison with Svelte feature-wise and story-wise (which is why I'm here!); Svelte is also easier than React. But the fact remains that I've invested significantly more time in Fulcro with much less to show for it. I do try to remind myself Fulcro is about the longterm scaling, though 🙂 I think a part of the problem for a beginner is that it is much tougher to "just put something up on the screen" and "fiddle" in Fulcro, since parts need to come together simultaneously. I realise that's an upfront price for saner development down the road. Pathom is a part of the struggle – yet another thing to learn/master. I realise I should use it to transform the backend data to the UI. But I've worked hard to find a database capable of modelling my data in such a way as to make it trivial to feed a UI, and – up to this point! and for my use case! – it is hard to see Pathom as more than an unnecessary layer in between. Ah, look. Not trying to say I'm right – I'm sure I'm not. I'm probably moving too fast in my learning and need to manage my expectations, and inexperienced enough to see the bigger picture and longterm benefits. Just need to persevere, and with the help of all you kind people, I just might.

Jakub Holý (HolyJak)16:12:49

You could say Asami is as good a fit as Datomic - yet RAD uses Pathom with Datomic. So I would not say it is an unnecessary layer. And as your case with "is it a string or a map? both!" shows, it may be really important to make the data usable. I would be interested to hear what was your learning path, where you see holes in it, and what is it you struggle most with.

zeitstein18:12:41

I don't doubt that, which is why I tried to qualify my statement. (Datomic is an even tighter fit with its pull syntax.) As for the string/map issue, I don't know what to think: it makes sense for my use case and there are no issues on the db side. The db produces an apt data tree and elsewhere I could handle the ambiguity with a single conditional on the client-side. (No matter, the fix was easy enough to achieve, since adding infrequently used attributes is not a big deal for Datomic-likes.) For learning, I've mostly stuck to the FDG for structure, branching out to other resources when I would get stuck. So, I've gone over most of the FDG, tutorial videos and your minimalist tutorials and workshop (big thanks!), and tried to apply the knowledge to the app I'm trying to build. As for the struggles along the way: • UI router – coming from Svelte's filesystem-based router, I've found it much harder to get to grips with Fulcro's. The Advanced Minimalist tutorial was invaluable to me here, because it gave a fairly short example, and that made it easier to grok than FDG. • Hooking up Pathom 3, and especially to use it purely client-side. • Pathom resolvers have generally proved tough. I've gone through the documentation and liked it, but I keep finding myself stumped when trying to do stuff in my app. • Generally, the breadth of things to learn and bringing it all together have been an issue. I've especially struggled when I would want to 'quickly' explore an implementation idea, for which I could find examples (like the string/map thing; but unlike, say, recursive queries which are explained very well in the FDG, like most things). Which exposes a lack of understanding on my part, clearly. I feel that I've tried to go over too much too quickly. I've been impatient to "put things on the screen" after (what feels like) weeks of learning. And this would cause frustration each time I would want to implement a new bit, because I would have to stop and go back to learning.

Jakub Holý (HolyJak)09:12:31

Thank you for the details! Regarding the map/string - it is actually simple IF you do not need the map to be normalized in the client DB. In that case you could query for :my/string-or-map and get the whole thing. It only gets complicated if you want it normalized for some reason - but then you need to accept that you must make the data fit Fulcro's model if you want to apply Fulcro's magic to it. 1. What has tripped you in the routing? Just that it is different from what you are familiar with? To me it seems quite natural and nice that I wrap the components in a router and let the components declare their route segment. 2. Hooking up Pathom 3 might have been an unnecessarily challenging task you gave yourself. You could stick with P2 and wait for the community of experienced practitioners to do the job 🙂 Don't try to put more on your already full plate if you don't need to 🙂 3. I would be interested in what is it that is hard with Pathom resolvers. A resolver is just a function that takes 0-1 input and optional parameters and returns a map of data. So I suppose the struggle is in mapping this to your data model and deciding what resolvers you need and how to break the data among them? Yes, there is a lot you can learn. I would recommend starting with something simpler, trying to avoid all advanced things (such as union and recursive queries, UISM, ...). Just the bare minimum, and getting comfortable with that. Only then would I try to do more. Otherwise it is easy to get overwhelmed, as you have seen. You could even drop routing and replace it with simple (if (some-condition) (ui-child-target1 props1) (ui-child-target2 props2) . Keep it simple, stupid as much as you can. Learn in small doses.

zeitstein19:12:52

I agree that I should accept Fulcro's model! 1. Took time to wrap my head around it conceptually and get it working with URLs, history, etc. 2. Most of what I needed was provided by Wilker's solution 🙂 3. Not quite. Currently I'm stumped with how to write a resolver which would handle a user-initiated load request. Probably very basic, will go back to docs again. I think I started simple and stupid. But, then I wanted to flesh things out more, so I had to deal with the router and recursive queries 🙂 Thanks for your help!

Jakub Holý (HolyJak)20:12:00

Could you elaborate on "handle user - initiated load request"? You mean a resolver to return the data that df/load! is asking for? Resolvers don't handle anything, they just return data based on inputs

xceno09:12:28

@U02E9K53C9L I've been down the exact same path as you. I was a full-stack dev, writing c#, java, js and ts for roughly a decade. I could crank out react UI's in a couple minutes (but I always wrote my own state management libs in the long term). Long story short, when I made the switch, I learned CLJ and Fulcro and Fulcro RAD and Datomic at the time, I think we can all imagine how well that went 😉 I was tempted for a few days to just say screw it and write my frontend in TS with a REST API in the backend the way I always did, but boy I'm glad I didn't. If you push through the learning curve of all the things you'll be rewarded with a very stable system that provides with so much nice things for basically free once you "get it". Anyway, what I actually wanted to say: • Keep the fulcro sources by your side and don't be afraid to just jump in and read them. • Try building a couple UI components and forms without RAD first, so you understand what goes into them. • When developing attributes and resolvers, use the parser directly to quickly test your code like so: https://github.com/fulcrologic/fulcro-rad-demo/blob/pathom3/src/datomic/com/example/components/parser.clj#L43. • Also, what I meant by splitting your values apart was: Split it apart in the pathom layer, you don't have to do it in asami or wherever. • (Advanced topic) When you're working with RAD, instead of "just" writing a pathom resolver, you can instead write a defattr with an attached resolver. The latter, makes it possible to use all kinds of RAD functions on it. But you can ask about this again when it's time haha. For completeness sake, here's how a pathom3 may look like for your case:

;; pco is an alias for: [com.wsscode.pathom3.connect.operation :as pco]
(pco/defresolver my-str-value-resolver
  "Returns the str value of a thing."
  [env {:my.thing/keys [id] :as input}]
  {::pco/output [:my.thing/string-value]}
  (let [value (queries/fetch-str-value env id)]
    {:my.thing/string-value value}))

(pco/defresolver pull-ref-value
  "Returns the entity referenced by the thing."
  [env {:my.thing/keys [id] :as input}]
  {::pco/output [{:my.thing/ref-value
                  [:another.thing/id
                   :another.thing/name]}]}
  (let [joined-entity (queries/pull-ref-value env id)]
    {:my.thing/ref-value joined-entity}))

;; How to test it.
;; parser comes from here:
;; 
(parser {} [{[:my.thing/id 1234]
             [:my.thing/string-value
              {:my.thing/ref-value
               [:another.thing/id
                :another.thing/name]}]}])
I'm not familiar with asami, but this example assumes that you have two different query functions one that returns your string, and the other a ref. Assuming you work of the fulcro-rad-demo: Don't forget to restart your mount state after registering and changing your resolvers and attributes, otherwise you won't see your changes.

❤️ 2
zeitstein14:12:57

@U0522TWDA, yes that's what I mean. Anyway, got it working now!

zeitstein15:12:38

@U012ADU90SW, thanks for the encouragement and advice 🙂 Hah, I was a bit 'smarter' than you – I 'learned' CLJ and Datomic first 😄 • Currently I'm not planning to use RAD. • About splitting attributes in the Pathom layer: yes, that's a good idea and I've thought about it. There's other factors making me hesitant to do it that way, but I'll keep it in mind.

Jakub Holý (HolyJak)15:12:56

Can I quote you 2 in MFT, like this:

## Next steps

First, a few words of wisdom:

> User X: I have to admit, though, I'm really struggling with Fulcro. Each time I want to add a new bit to my app, it's a struggle. I'm losing my motivation. It took me three days to get to a point with Svelte I'm still not close to reaching after two weeks with Fulcro. Just can't seem to get a solid grip on everything.
>
> xceno: @X I've been down the exact same path as you. I was a full-stack dev, writing c#, java, js and ts for roughly a decade. I could crank out react UI's in a couple minutes (but I always wrote my own state management libs in the long term).
Long story short, when I made the switch, I learned CLJ and Fulcro and Fulcro RAD and Datomic at the time, I think we can all imagine how well that went 😉
I was tempted for a few days to just say screw it and write my frontend in TS with a REST API in the backend the way I always did, but boy I'm glad I didn't. If you push through the learning curve of all the things you'll be rewarded with a very stable system that provides with so much nice things for basically free once you "get it".
?

👍 1
xceno15:12:51

sure thing, I wanted to contribute anyway haha

🙏 1
Benjamin C16:12:28

Does fulcro have something built-in for triggering changes from the server-side?

Benjamin C17:12:52

Ah, seems this might be what I was looking for: https://github.com/fulcrologic/fulcro-websockets

👍 1
Michael W17:12:42

Is there an example of passing settings into a ui layout? I have created a new :grid control based on :list from semantic-ui, and would like to pass it the column layout as a hint, so it can create the column widths dynamically.

Jakub Holý (HolyJak)18:12:53

You are talking about RAD, right? I suggest looking at the code to find out. It's do it but am off PC :-( Though I'm not overly optimistic.

Michael W18:12:45

I have been looking for an example in the demo, but I'm just not understanding it I guess. I'll keep looking, my control works well enough, I just want to make it a little more dynamic.

Jakub Holý (HolyJak)20:12:54

I don't think there is an example there m you must study the underlying code.

Jakub Holý (HolyJak)17:12:07

Have you figured it out?

tony.kay19:12:39

yeah, just look through the semantic UI source. https://github.com/fulcrologic/fulcro-rad-semantic-ui/blob/develop/src/main/com/fulcrologic/rad/rendering/semantic_ui/form.cljc#L80 If you're doing a layout, it's a bit more tricky to customize because of the nested nature (if you're talking forms). See the report implementation for similar stuff on reports.

tony.kay19:12:31

because it is plugin-based there's sort of a back-n-forth between the RAD core code and the plugin. The core code looks up the plugins and calls them, but sometimes the plugin has to ask the core to do that lookup, etc. It makes it a little harder to trace than normal code 😕

xceno13:12:22

I've built a custom color-picker control for RAD that I wanted to open source ages ago. I've posted a gist here: https://gist.github.com/Xceno/a476dfd32b1a09b9e2999509d041b5a7 I think you should be able to build an extended version of the layout function and simply register it as well. Maybe even override the default implementation with your own.

Michael W15:12:40

@U012ADU90SW Thank you. This is very promising, my own addition is a new report layout, not a picker control, but your example gives me a good idea of how it might look to deal with passing settings into a report.