Fork me on GitHub
#fulcro
<
2021-07-01
>
cjmurphy08:07:09

In Fulcro RAD, can anyone help me with disabling (or even making invisible) a subform? The situation is that I want file upload functionality (the subform) to only be available when a particular form attribute is valid. I can already test for this attribute's validity with (attr/valid-value? attribute attrib-value). For experimentation purposes I did this from an fo/field-visible? function on a 'random' scalar attribute. If fo/field-visible? worked with a ref attribute then I'd be fine.

dgb2310:07:26

disclaimer: I’m a complete newbie. My intuition is that the validity of your field should be encoded in the data-model from which the other component pulls this information. Is that so?

cjmurphy10:07:01

The file to be uploaded is a csv file and the attribute that needs to be valid is the headings of that file. I want the user to be required to input valid list of headings (eg./ date,desc,money) before uploading the file becomes possible. This might mean the upload button becomes enabled. I want to develop an intuition for solving these kinds of problems using the mechanics available in Fulcro RAD.

tony.kay02:07:59

The built-in renderer doesn't have the concept of hiding an entire subform. remember that RAD's model is one of gradual "outgrowth". In this case since the renderer does not support what you need, your choices are: 1. Create your own rendering plugin (based on the existing one) and add what features you need 2. Submit a PR to the built-in one. I'd be willing to add support for that on the ref attributes or maybe better as a lambda on the subforms options. 3. Add a custom ref container style. That let's you customize how refs are rendered. This is related to 1/2. 4. Take over the rendering of the form by simply giving the forms a body, in which case you can do whatever you want.

tony.kay02:07:54

Take a look in RAD form.cljc:127, ref-container-renderer

👍 4
tony.kay02:07:31

and its usage in the SUI plugin form.cljc line 212

sheluchin10:07:02

Is it reasonable for some UI changes to not go through the transaction system and simply update the DOM without being reflected in the client DB, or is it better to adhere to the practice of tracking all UI state changes as changes to the client DB?

tony.kay02:07:28

Items that come to mind: • Using React state for fast local updates, commonly useful for: • Using local state for things like animation control, etc. • Storing certain things in atoms for global access, such as a map from React refs to components so you can do things like manage focus or attach hovers to the DOM via external libs So, yes, there are definitely use-cases. However, if you are rendering a core bit of your application domain, you'll be better served using Fulcro for that

sheluchin13:07:46

Thanks @U0CKQ19AQ. I'll lean heavily towards using Fulcro except for the most inconsequential UI changes.

dgb2310:07:27

Generally, whether you use fulcro or vanilla JS, you might end up using the DOM as your DB, which inherently corrupts your data/state except you synchronize everything nicely, which is something that proliferates very, very quickly and creates hard to track down bugs.

dgb2310:07:58

hacks can be valuable short term. but frameworks like fulcro make it so easy to separate these things so why go there? I guess if you integrate third party code.

Nikolas Pafitis13:07:25

Hello guys, new to fulcro, coming from reagent + re-frame. I've seen Tony Kay's videos, and read some of the other guides/examples and one thing i noticed is that every component is created using the defsc macro, thus having components with big bodies made of primitive html elements. Is it an anti-pattern/invalid to just use reagent-style functions for stateless components? e.g

(defn label [text]
   (dom/div :.label-class text))

tony.kay02:07:05

Making little wrapper functions to emit some (common) reusable DOM is fine. You can also use the new raw components support in 3.5 to do pure hooks-based low-level functions. I've not documented those very well yet, but the new stuff is quite powerful and less verbose. See raw-components/nc, hooks/use-component, etc. defsc gives you the rough equivalent of a React PureComponent with automatic shouldComponentUpdate, and more importantly integrates you with the graph query and data normalization...but sure, it is quite common to just write some boilerplate UI in a plain function and just call it.

donavan13:07:59

It depends. There is no magic Reagant style conversion of functions into classes (the major upside to this is your stack traces are not full of Reagant internals). But you can write a defsc with no query, ident, etc. to get what you want:

(defsc label [{:keys [text]}]
   (dom/div :.label-class text))

Jakub Holý (HolyJak)16:07:10

To clarify: You still need 2 arguments, ie [_ props]

donavan13:07:06

…using a defn as a component is probably not doing what you think it is

Nikolas Pafitis14:07:31

Thanks for the clarification

donavan14:07:44

Just don’t forget, you still need to use comp/factoryto create a ui-label . Again, there is less magic than either Reagent or React with JSX

Mardo Del Cid14:07:37

Hi everyone, I was having a look to https://www.apollographql.com/docs/react/, and they do this “declarative data fetching” thing: > Declarative data fetching: Write a query and receive data without manually tracking loading states. I was wondering if something like this could be done for fulcro? The defsc components know their query and its parameters, and they could register to some “I’m going to appear” hook (via component will mount lifecycle, deferred routing, etc). Then fulcro could aggregate all necessary data to fetch, and exclude anything that’s already loaded in the client’s DB (not sure how to handle invalidating cache here). Fulcro could also figure out if we’re requesting an attribute for a specific ident (i.e. We have many components that display [:user/first-name] , but only one with ident [:user/id 2] shows the user’s cars [:car/id :car/model], and again, ignore it if already present in the client’s DB). All aggregated queries would look like this:

{[:user/id 1] [:user/email :user/role :user/first-name] ...
 [:user/id 2] [:user/email :user/role :user/first-name [{:car/id [:car/model]}]] ...
 [:item/id 1] [:item/price ...}
And then be auto merged to their corrects client paths (also taken from each defsc’s ident). defsc could have a join in their query on some autoloading namespace (kind of like the forms config join) that give it, say, {:state :loading/failed/completed}. For pathom params, they could be in the defsc’s queries (i.e. pagination params) and whenever they change, a new fetch would be triggered. Auto-fetching should be opt-in, of course. It is nice to have the possibility to fetch the data manually and is a good default behavior. I just wanted to share some thoughts, I’m very new to fulcro (around 2 weeks of learning) and not sure if this goes against fulcro’s philosophy, if it is even possible and/or would break things 🙂. Could something like this be done for fulcro?

Jakub Holý (HolyJak)16:07:12

Fulcro already has built in loading state tracking, but it's opt-in - see the load marker param to load! Fetching in willMount can lead to cascading remote calls - A will mount and loads its data, it renders its child B that will load its data,... Loading the whole relevant subtree for all the child nodes at once eg using dynamic routing and :will-enter will give you much better performance. And works just fine in practice, I haven't felt any need for auto loading.

dvingo16:07:59

This is a pretty interesting idea, one thought of how to go about it is to add a new property to defsc like ::subscriptions or whatever name you want, which would be the opt-in parts of the graph that result in remote calls. This could be a data structure like a normal fulcro query but maybe has which remote to use and the caching policy to use. You can then make your own macro that wraps defsc and handles the details of fetching the data. Notes on how to wrap defsc can be found in this talk by Wilker: https://youtu.be/R_XPwi0Kiwk?t=246

Mardo Del Cid16:07:13

Hi @U0522TWDA, thanks for your response 🙂 Yes, I’ve seen it, I think you’re the author, right? Thanks a lot for such a valuable learning resource 😄 Yeah, I read about the cascading issues in the tutorial, and I thought auto-loading could be done without cascading, some work on the backend may be required, to have a bulk query resolver and recursively resolve queries using IDs from previous resolved queries in the same bulk query request. I also have seen the loading tracker state via markers, and this could be used as a join in defsc queries to get the request state, but would be nice to be auto-hooked for bulk query “global” request. My point with this post was not having to write such code. I liked apollo’s declarative data fetch, where you don’t need to think about handling the fetch itself, it just happens and is there for you for you to use in the view. I mean, I’m ok writing such code to manually load the data, but wanted to explore the possibility of having declarative fetching in fulcro, as component’s know all data they need in the UI tree. 🙂

👍 4
dvingo16:07:28

as Jakub mentioned, there are existing pieces in fulcro that should make adding this sort of thing pretty straight forward (the remotes abstraction and load state tracking)

Mardo Del Cid17:07:51

@U051V5LLP Yeah, I figured it should be something easy to do with current fulcro’s building blocks. I’ll try something to go over defsc’s metadata and traverse the UI tree queries and see what happens 🙂 Subscriptions would be nice, but that’s a complete different beast. However, as Tony said in one of his videos, Fulcro is ready for such real-time features to be plugged easily, the hard part is to notify the client when the data changed in a scalable way 😛

dvingo17:07:07

Ah, I didn't intend for subscription to imply live updates, just a way to distinguish the parts of the query that you want to result in remote calls

Mardo Del Cid17:07:36

Ah got it, yeah. I think wrapping defsc in a macro is an overkill. I think traversing defsc’s metadata should be doable, I’ll check the video you suggested ❤️

dvingo17:07:40

I think wrapping defsc is the way to go, one of the great parts of fulcro is that the component options is an open map, so you can add whatever extensions you want there. Here is an example adding re-frame subscriptions by wrapping defsc and adding a new key, if you want some inspiration: https://github.com/dvingo/fulcro-re-frame-playground/blob/main/src/main/dv/fulcro_re_frame/play/client/fe_macros.clj#L134

Mardo Del Cid17:07:17

Nice! Yeah, I thought it was going to be an open map with data everyone can just use 🙂 — fulcro was created by a clojurist after all 😉 As a second thought, I think you’re right. Maybe I need a macro because I’d need to hook the “I’m going to show” callback (whatever that will be), and for that I’ll need to change the component somewhat. Another option would be to let the user handle such hook, and call a function to register to the global bulk query request that is going to happen.

Mardo Del Cid17:07:40

after writing that I think I prefer letting the user decide when to register his component 😛

Mardo Del Cid17:07:10

or both… 🙂 … anyway, thanks a lot for your input! ❤️

dvingo17:07:12

for sure! I'm interested to see what your explorations uncover 🙂

tony.kay02:07:45

I would encourage you to look at the new hooks and lightweight/raw component stuff in 3.5. Subscriptions have a lot of complexity to their simplicity, and I decided long ago that providing them "out of the box" was a bad idea...too many things to configure, opinions to satisfy. It's actually quite trivial to implement them, dependning on your requirements. The React effects hooks, combined with raw/nc and hooks/use-component can get you quite far.

tony.kay02:07:03

I'm sure coming up with a use-subscription would take all of about 30 minutes for the base case. I've got a component I wrote for doing a dropdown that auto-loads the options that looks like this:

(defsc ProductInventory [this {:product/keys [id]}]
  {:use-hooks? true}
  (let [Inventory (hooks/use-memo (fn [] (rc/nc [:product/id
                                                 :product/sku
                                                 [df/marker-table ::inventory]
                                                 {:product/calculated-stock [:location/id :location/name :inventory/quantity :inventory/unit-cost]}]
                                           {:initial-state (fn [& _] {:product/id               id
                                                                      :product/calculated-stock []})})))
        {:product/keys [sku calculated-stock] :as props} (hooks/use-component (rc/any->app this) Inventory {:initialize?    true
                                                                                                            :keep-existing? true})
        loading?  (df/loading? (get props [df/marker-table ::inventory]))]
    (hooks/use-effect (fn []
                        (df/load! this [:product/id id] Inventory {:marker ::inventory})
                        identity) [])
...

tony.kay02:07:27

nc lets you generate a dynamic component on the fly with auto-generation of ident based on the first ID field found in the (nested) query. It actually generates a TREE of anonymous components for normalization. Then you can hook that up with use-component to get the data from state, and use-effect to trigger the load. Generalize that and you've got use-subscription

tony.kay02:07:02

arguments are: tree query, id of thing to load. Output is the props of the loaded thing.

tony.kay02:07:33

use-memo is just so it doesn't keep generating new anonymous components on every render

Mardo Del Cid03:07:03

@U0CKQ19AQ The legend himself has graced us with his presence! 🙂 Thanks, Tony. I was actually looking at rc/nc because I wanted to mimic what you did with form-config-join, which uses it. Wasn’t quite sure what it did, but after your explanation I think I get it. However, I’m building a toy project first before going deep into fulcro’s internals. There are a lot of concepts I need to get my head around still. Thanks for the amazing framework, I have gone through a cycle of “screw this, it’s too overwhelming” and coming back again because all design decisions are just beautiful, every bit there that’s “hard to learn” is there for a reason, to make things simpler in the grand scheme of things… banging my head against the wall has been worth it every single damn moment ❤️

dvingo16:07:04

this is really cool! I have try out these nc and hooks tools..