Fork me on GitHub
#fulcro
<
2022-03-21
>
Karol Wójcik08:03:44

I have a Container component that wraps a form. How can I put the query of the child form in the Container?

(defsc ComponentChildForm
  [this {:keys [:some.ident/uid :ui/some-param] :as props}]
  {:ident :some.ident/uid
   :query [:some.ident/uid
           :ui/some-param
           fs/form-config-join]
   :form-fields #{:ui/some-param}}
  (dom/div {} "A component chilld form"))

(def component-child-form (comp/factory ComponentChildForm))

(defsc ComponentContainer
  [this props]
  {:ident :some.ident/uid
   :query [:some.ident/uid
           :some.ident/name
           :some.ident/last-name
           ;; How can I join form props here?
           ]}
  ;; Some ifs here
  (cond 
   (blabla? props)
   :else (component-child-form props)))

aratare08:03:27

Wouldn't that just be a normal {:child-data (comp/get-query ComponentChildForm)}?

👍 1
Karol Wójcik08:03:59

But how can I fill that data?

aratare08:03:06

You mean how to extract out the data you want to pass to the child? In that case child-data is just a key in the parent's props, so you can do something like (component-child-form (:child-data props))

👍 1
Karol Wójcik08:03:31

I mean something else. The :child-data is not reachable. I can't find it in the props.

aratare08:03:01

So it would be something like

(defsc ComponentChildForm
  [this {:keys [:some.ident/uid :ui/some-param] :as props}]
  {:ident :some.ident/uid
   :query [:some.ident/uid
           :ui/some-param
           fs/form-config-join]
   :form-fields #{:ui/some-param}}
  (dom/div {} "A component chilld form"))

(def component-child-form (comp/factory ComponentChildForm))

(defsc ComponentContainer
  [this {:keys [child-data] :as props}]
  {:ident :some.ident/uid
   :query [:some.ident/uid
           :some.ident/name
           :some.ident/last-name
           ;; How can I join form props here?
           {:child-data (comp/get-query (ComponentChildForm))}]}
  ;; Some ifs here
  (cond
   (blabla? props)
   :else (component-child-form child-data)))

Karol Wójcik08:03:55

Yep I got this part. However, I'm not seeing that data in props.

aratare08:03:43

Is your ComponentContainer's query being composed correctly all the way to the root component?

Karol Wójcik08:03:04

I think the approach with child query is something I don't want in this case. The :child-data makes an :child-data entry in this entity. What I'm trying to achieve is to put some extra properties in :some.ident/uid table in-place.

Karol Wójcik08:03:16

This works:

(def ChildFormQuery [:some.ident/uid :ui/some-param fs/form-config-join])

(defsc ComponentChildForm
  [this {:keys [:some.ident/uid :ui/some-param] :as props}]
  {:ident :some.ident/uid
   :query #(-> ChildFormQuery)
   :form-fields #{:ui/some-param}}
  (dom/div {} "A component chilld form"))

(def component-child-form (comp/factory ComponentChildForm))

(defsc ComponentContainer
  [this props]
  {:ident :some.ident/uid
   :query #(concat [:some.ident/uid
                    :some.ident/name
                    :some.ident/last-name]
             ChildFormQuery)}
  ;; Some ifs here
  (cond
   (blabla? props)
   :else (component-child-form props)))
But I'm not sure whether it's a good way to do it.

aratare09:03:34

Ah I see. I'm not sure about this. Perhaps @U0522TWDA or @U0CKQ19AQ can chime in and give us some suggestions?

Hukka09:03:45

Would have to see how you ingest data / the fulcro db, and how the whole chain of prop passes has been done. But it sounds like your data for the Container is flat, so there is no join

Hukka09:03:31

A bit similar case, though the child here is not singular, in our code

(defsc Leg [this {:leg/keys [id type origin destination] feature-flags :feature-flags}]
  {:query [[:feature-flags '_] :leg/id :leg/type :leg/origin :leg/destination]
   :ident :leg/id}
…)

(defsc Plan [this {:keys [legs]}]
  {:ident (fn [] [:component/id ::Plan])
   :route-segment ["planner"]
   :query [{:legs (get-query Leg)}]
   ; Initial state is needed since root will generate a data link here,
   ; but it can be empty and doesn't need to refer any children
   :initial-state {}}
 …)

…
(fulcro.merge/merge-component! app/app Leg leg :append (conj (get-ident Plan {}) :legs)) ; this is in a doseq going through incoming data, with leg defined as the data object
…

Hukka09:03:20

So what that does is that it creates a new entry in the fulcro db under the ident that is defined in Leg, and then appends it to the Plan's data under the right collection. Then where ever the Plan is in the UI tree, if the props path to it works, it will also work to the Legs

Karol Wójcik09:03:38

Hmm.. @U8ZQ1J1RR your case a little bit different than mine. My case is something like: I got a Leg component and I want to separate it into two LegView and LegNewCreate. The LegContainer is a component that conditonally switches between those two and that's precisely why I need to merge two queries of LegView and LegNewCreate together into one at LegContainer.

Hukka09:03:58

Could you just query for both data always?

Hukka09:03:12

Are you using a Pathom remote, or just merging data in manually?

Karol Wójcik09:03:29

Pathom remote. 😄 I think placeholders is what I was looking for.

Hukka09:03:27

Should make this pretty easy, as you don't need to shape your data to match the UI manually

Karol Wójcik09:03:29

Oh. Now I understood your question. Placeholders doesn't works when you merge data manually? Actually this is my case.

Hukka09:03:05

Yeah, because the fulcro takes the placeholder symbol literally, so the fulcro db really has to have that key

Hukka09:03:03

That is, fulcro db must match the UI tree structure exactly (though it is flattened, the join keys must exist). Something needs to translate into that from the "pure" data model. Pathom makes it pretty easy and automatic, otherwise the data ingestion needs to be changed when the UI structure changes (though the flattening helps here a lot)

Hukka09:03:43

In your case the data for the container needs to have both the :view and :new-create keys, though the value could just as well be identical and point to the same entity. Different child components would then use subsets of that entity's data

Karol Wójcik09:03:19

Got it. I think, I will soon refactor it to a Router strategy. It seems an elegant solution.

Hukka09:03:21

Or alternatively you would need to work with dynamic queries. Or indeed combine the fields that you need

Hukka09:03:35

Yeah, routers handle the dynamic queries for you

Karol Wójcik09:03:08

Thanks for all the help @U8ZQ1J1RR 🙂

Hukka09:03:18

No problem, good that I made sense 🙂

Hukka09:03:57

I have spent quite a while rubbing my face while trying to work with the data modelling :face_with_rolling_eyes:

Jakub Holý (HolyJak)15:03:17

Awesome to see folks helping each other and sharing useful (my 😊 ) resources 🙂 I would not add routers until there is a real need for them, they bring a new set of complexity. > Placeholders doesn't works when you merge data manually? Actually this is my case. Placeholders are implemented by Pathom. If you get the data from Pathom you are good. If you merge/merge! or merge/merge-component! them then you have to replicate what Pathom does yourself. But it is just adding an extra edge to the client DB, which is trivial. I would rather do that than bring in routers just for this use case.

Karol Wójcik06:03:22

Thanks @U0522TWDA. This is exactly what I'm going to do in this case! Thank you so much!

👍 1
tony.kay17:03:19

@karol.wojcik so I’ll let the thread stand regarding your original question, but I did want to chime in about a number of things I see in this code same that you should correct: 1. An ident is a Fulcro concept, and is not meant to be a reified thing in your data nameing. E.g. :some.ident/uid is a very poor choice for a prop. When giving examples or writing them you should work with real concepts so you can give them good names. E.g. PersonForm and :person/id. Mixing “ident” into the names in your data model is confusing and will lead to additional misunderstandings. An ident (also known as lookup-ref in Datomic) is a way to refer to a specific instance of an entity by a natural key…`[:person/id 42]` , so if you start mixing the work “ident” into your data naming, then people will expect your prop to CONTAIN an ident, which is confusing…an ident with the content [:person.ident/id 42] vs {:person/ident [:person/id 42]}…etc. Just don’t do it. 2. :form-fields is meant for listing things you want Fulcro’s form state ns to manage; however, the ui namespace prefix tells Fulcro’s I/O system to ignore it. This is so you can mix pure UI concerns with full-stack ones in the same component without having to write defensive code on the server to ignore them. (i.e. :ui/some-param will NEVER appear on a network request for a load). Thus, by choosing that naming for your form field, you’ve made it impossible to load that value from a server. Technically form-state will allow it (as a local-only field), but it is probably not what you meant to do, and is a bad habit in examples.

❤️ 2
Karol Wójcik06:03:34

1. Thank you for the explanation. I'm still new to Fulcro, so some of the concepts are still misunderstood by me. Thanks for this valuable lesson. 2. Actually, :ui/some-param is a value of part of the composite type fetched from remote. Maybe let's break this example so that I can learn what is the Fulcro way of managing form state. Supposing I'm creating a CMS. I want the admin to manage the user settings. How the form for :person/settings should look like?

{:person/id "123123"
 :person/settings {:person.settings/status "active"}}

Jakub Holý (HolyJak)10:03:48

The first question to ask is, what do I want the data to look like? After you know what data you have - in this case, what settings - then you can consider how to show / edit them in the ui. I am not really sure what you are asking here. What data should be in person/settings? That is your business decision. How to structure them? That depends on the data. Is a flat map enough or do you need more structure? Or are you asking how to edit that data in a form?

Karol Wójcik08:03:41

The questions is more like: You have a :person/settings where the each setting is not a separate entity (no unique identifier provided), but rather a plain KV tuple. Supposing the requirement is to show a form with editable inputs of :person/settings. :person/settings

{:person/settings {:status "active", :sex "male"}}
How can I have leverage the Fulcro subforms in this task?

tony.kay17:03:16

One other thing: The various namespaces of Fulcro, while taking advantage of Fulcro’s features, are by no means the only way of doing things. This applies to nses for routing, forms, state machines in particular. Those are there to show one way of handing those tasks, and I personally use them all to good effect; however, do not approach forms as “I MUST use form-state or I’m doing it wrong”. I’d recommend starting out by ignoring these nses and trying to do it on your own. It isn’t hard to write a form without form-state. What happens, though, is you start to see repetitive patterns (like wanting to undo a change), validation, and save/load. Ultimately, RAD forms are much more powerful, because they do almost all of it for you, but the existence of those should not keep you from trying very simple things “solo”, without the help of those libraries…when learning it is very useful to understand the primitives before using the add-ons.

tony.kay17:03:19

Fulcro, in particular, is a library where you should learn the core primitives in deep detail before building advanced things. Query/ident/initial state, load, mutate, merge, data targeting. Get to know those in intimate detail.

tony.kay17:03:01

Then the other stuff just becomes “convenient stuff” you can pick and choose from as appropriate.