Fork me on GitHub
#fulcro
<
2018-05-29
>
OliverM08:05:14

What's the best way to pre-populate the database with normalised data on startup? Can you use the :shared entry to :reconciler-options? From the docs it looks like data there isn't normalised into the database

claudiu08:05:00

@oliver.mooney What's your use-case ? If you have the data on the server when rendering the html you can use intial-state https://github.com/fulcrologic/fulcro-template/blob/master/src/main/fulcro_template/client.cljs#L20

OliverM08:05:25

@claudiu that looks interesting. I'm trying to pre-populate the client db with the last few things the user worked on as that's the most common user behaviour, and it'd save a few network trips. I've no SSR in place yet

OliverM08:05:34

@claudiu that seems to work well on initial inspection, thanks!

claudiu09:05:42

Cool :) u're welcome

OliverM12:05:08

How do you refer to a ident query in a component's props? You can't destructure the ident as a key in a {:keys []} vector

claudiu12:05:20

what are you trying to do ? šŸ™‚

wilkerlucio12:05:07

@oliver.mooney (fp/get-ident Component props), but most of the time you should not need it

OliverM12:05:19

I think it's something i'm missing - it's not something I'm actually trying or needing to do, I'm just testing my understanding of how the props used in the component's render call, and the fulcro query, ident and initial-status all relate to each other. My test case is showing a summary of a select. I guess it's easier if I just show some code...

(defsc OptGroup
  [this props]
  {:ident [:optgroup/by-id :optgroup/id]
   :query [:optgroup/id :optgroup/caption :optgroup/options]})

(def select-table :bs4-select/by-id)

(defn change-select-value* [st id value]
  (assoc-in st [select-table id :select/value] value))

(defmutation change-select-value [{:keys [id value]}]
  (action [{:keys [state]}] (swap! state change-select-value* id value)))

(defsc BS4Select
  [this {:keys [select/id select/caption select/value select/options]}]
  {:ident [select-table :select/id]
   :query [:select/id :select/caption :select/value
           {:select/options (prim/get-query OptGroup)}]}
  (labeled-input
    {:onChange #(prim/transact! this
                  `[(change-select-value {:id ~id
                                          :value ~(.. % -target -value)})])
     :type "select"
     :select-options options
     :value value}
    caption))

(def ui-select (prim/factory BS4Select {:keyfn :select/id}))

(defsc SelectRoot
  [this {:keys [] :as props}]
  {:query [{[:bs4-select/by-id :select-test] (prim/get-query BS4Select)}]}
  (let [select-data (get props [:bs4-select/by-id :select-test])]
    (dom/div
      (dom/div "")
      (ui-select select-data))))

wilkerlucio12:05:48

I think worth keeping in mind the different between normalised vs denormalised DB

OliverM12:05:11

So in the select root I was hoping to pull some prop from the select data (like the current choice) and display it as a summary - it works first time but doesn't update when the select is changed to a different value.

OliverM12:05:55

@wilkerlucio yeah I was thinking it must be something to do with that, but thought there should be a way to dive into the :query in the render's props to pull it out at the same level

wilkerlucio12:05:48

@oliver.mooney ok, one thing that helps is that you most of the time want the component which the state is going to be modified to be the one who triggers the transaction

wilkerlucio12:05:11

in your case, the option is triggering the transaction, but the select is the one who needs the state to be changed

wilkerlucio12:05:39

this is usually better solved by sending a callback function from your select to your option, and call it there, so the select is the one who actually calls transact!

wilkerlucio12:05:33

what matters who calls the transaction is that when you (transact! this ...), there will be a key called :ref in your environment, which is the ident of the component who did the transaction, you can leverage that to create generic transactions

wilkerlucio12:05:11

sorry, I misread your code, your tx is gonig into the right place šŸ™‚

wilkerlucio12:05:21

let me try to simplify the code you posted, and tell me if that makes sense

wilkerlucio12:05:41

(defmutation change-select-value [{:keys [value]}]
  (action [{:keys [state ref]}]
    (swap! state update-in ref assoc :selected/value value)))

(defsc BS4Select
  [this {:keys [select/id select/caption select/value select/options]}]
  {:ident [:bs4-select/by-id :select/id]
   :query [:select/id :select/caption :select/value
           {:select/options (prim/get-query OptGroup)}]}
  (labeled-input
    {:onChange #(prim/transact! this
                  `[(change-select-value {:value ~(.. % -target -value)})])
     :type "select"
     :select-options options
     :value value}
    caption))

wilkerlucio12:05:45

just the mutation part

wilkerlucio12:05:10

check that using ref we can avoid having to know about the ident key of the element, this will target the correct data

wilkerlucio12:05:23

that is in fact so common, that you Fulcro has a simpler way to do it:

wilkerlucio12:05:10

:onChange #(mutations/set-value! this :select/value (.. % -target -value))

OliverM12:05:35

oh, you can get the ref from the environment! I didn't know that

OliverM12:05:43

that's very handy

wilkerlucio12:05:12

and if you are using dom/select, just be aware your values must be strings

OliverM12:05:32

yeah I noticed that but figured it wasn't related

wilkerlucio12:05:32

and about the normalised/denormalised, the database saved on your reconciler is a the normalised (which is the one who looks like graph, with idents and things...), when you are transacting, this is the one you get

wilkerlucio13:05:10

but during rendering, you get the denormalised (also called "tree"), which is just the map you expect to be passing on React components

wilkerlucio13:05:05

if you need to change formats, there are some handy functions in fulcro, check for prim/db->tree and prim/tree->db, but most of the time you have the right format in hand šŸ˜‰

wilkerlucio13:05:50

for the initial state, you send the denormalised, and fulcro will normalise it, unless you send the :initial-state as an atom, then you should normalise it yourself

OliverM13:05:05

So the render call has the denormalised version of whatever you had in the query, regardless if the query had idents or links or whatever else?

wilkerlucio13:05:23

no, the denormalised is constructed from your query

wilkerlucio13:05:39

so if you omit fields on your query, those are going to be missing in your props

wilkerlucio13:05:12

imagine like the normalised is like the database, and you run your component queries against it, and it will only return what you expressed there

OliverM13:05:15

yeah that makes sense - and if I log the result of the (get-props...) call in my SelectRoot component above I see the denormalised data

OliverM13:05:45

I'd been assuming that you had to have the keys of that get-props result in your render props for the component to update when they changed

wilkerlucio13:05:46

it's (fp/props), get-props is something else, but I don't remember what it is

OliverM13:05:20

sorry yes, I mistyped - I meant the (get props [:bs4-select/by-id :select-test]) part

wilkerlucio13:05:45

hahaha, no problem šŸ™‚

wilkerlucio13:05:27

your props will be whatever your query mentions (unless you break the chains and tumper on it yourself on the way)

wilkerlucio13:05:46

and you should avoid that, if you need to send extra things (that aren't part of the db), use computed

OliverM13:05:21

Is computed only for things that need to be "computed" for each child of a parent, or grandparent etc? I was trying to work out if it was only event handlers that varied by child in practice

OliverM13:05:16

oh I see your edit now, that makes sense

wilkerlucio13:05:39

most of the time is for event handlers

wilkerlucio13:05:32

but I had some exceptions, sometimes I also use to send extra customization for the component, like override styles

OliverM13:05:45

thanks for all of the explaining! Things are clearer now, I'll play with it a bit more

wilkerlucio13:05:27

have fun šŸ™‚

šŸ˜€ 4
tony.kay14:05:37

@oliver.mooney Iā€™d add this: computed is for any data being passed from parent to child that did not come from the childā€™s query. That includes callbacks and anything else the parent ā€œcomputedā€ for the child. It has to do with rendering optimization: Fulcro can re-render a child that has an ident, but it can only supply data that the child asks for (not data that the parent previously passed). To make it work, Fulcro remembers the computed stuff for these optimized renders from the prior parent render. If youā€™re using keyframe rendering mode, for example, then you donā€™t need it (but your UI will likely be a lot slower).

šŸ‘ 4
OliverM14:05:38

good to know @tony.kay, thanks - so if switching to the keyframe rendering mode gets callbacks etc working you've a signal that computed is needed too

myguidingstar14:05:22

say I have a Blog component like in section 9.9.2 Filling in the Subgraph of the book

myguidingstar14:05:36

how do I set params to the sub query :blog/comments using df/load so I can put it in started-callback?

tony.kay14:05:19

@oliver.mooney I guess you could use that as a debugging step šŸ™‚ The rule is simple, though: if it isnā€™t in the query, it belongs in computed.

šŸ‘ 4
tony.kay14:05:34

@myguidingstar (load :blog/comments Comment {:params { ... } :target [...]}), or do you mean something else???

myguidingstar14:05:28

technically it should send something like this to server: {[:blog/by-id 1] [:db/id {(:blog/comments {:offset 10 :limit 10}) [:db/id :comment/body]}]}

myguidingstar14:05:35

hmm, I didn't think of :target. Thanks

wilkerlucio14:05:46

@myguidingstar I feel you, I'm just dealing with the same case

šŸ™‚ 4
wilkerlucio14:05:02

@tony.kay the issue here is that we want to parameterize some part of the sub-query

wilkerlucio14:05:20

the :params on load only goes into the join root of the query

tony.kay14:05:11

(load this [:blog/by-id 1] Blog {:params {:offset 10 :limit 10}})

tony.kay14:05:53

well, you can use dynamic queries

tony.kay14:05:58

they are honored by load

tony.kay14:05:17

or you can use the load mutation directly with an arbitrary query

tony.kay14:05:21

My original thought was that these ā€œinitial loadsā€ have reasonable ā€œdefaultsā€. Thereā€™s really no need to keep loading Blog 1 with different parameters of children. You load the Blog and it defaults to ā€œnoā€ or ā€œ10" commentsā€¦then you update the stream of comments

tony.kay14:05:55

you can also pass the parameters to the blog join and pass those parameters down to children

wilkerlucio14:05:03

do you know if using load-field, the params will go on that field?

wilkerlucio14:05:10

or they will go on the ident join?

tony.kay14:05:35

pretty sure they go on the join

wilkerlucio14:05:42

I guess so too

wilkerlucio14:05:49

since it's just a sugar for the regular load

wilkerlucio14:05:26

I wonder if we could do something like: :subquery-params {[:blog/comments] {:foo "bar"}}

tony.kay14:05:53

too swiss-army knife

wilkerlucio14:05:19

I'll probably write something around load to do that, then I can tell if that works after some time šŸ˜›

wilkerlucio14:05:36

this is my current best solution, do you have other ideas?

tony.kay14:05:54

the load mutation itself allows an arbitrary query. If you have something that needs to muck with an arbitrary query, you should probably use that.

wilkerlucio14:05:28

I already use a custom load function that wraps fulcro load (because I add some app-specific things around that), so I'll try adding it there

myguidingstar14:05:56

what is "the load mutation"?

tony.kay14:05:07

@myguidingstar is that not in the book?

wilkerlucio14:05:14

@myguidingstar it's the mutation that fetch/load calls underneath

wilkerlucio14:05:33

if you look at load impl you'll see it

tony.kay14:05:45

I guess it isnā€™t exactly encouraged šŸ™‚

myguidingstar14:05:47

ah, I didn't read it šŸ™‚

myguidingstar14:05:38

but what about normalization?

tony.kay14:05:46

it is the underlying mechanism behind load and load-field. The latter two are just helpers for common cases.

tony.kay14:05:57

Normallization is a product of using componentā€™s to build the query

tony.kay14:05:07

so be sure to use queries from components šŸ™‚

tony.kay14:05:27

Ah, which brings me back to dynamic queries

tony.kay14:05:42

you want the parameters on the component being used

wilkerlucio14:05:01

@myguidingstar I don't recommend, but the truth is that the only different from fp/get-query is that it adds a :component meta to the query root, you can do that by hand too

tony.kay14:05:23

Fulcroā€™s client-side query processing ignores parameters by default, so a set-query! on the Comments component could be used to add params that would show up in the load

wilkerlucio14:05:46

@tony.kay I'll try to check that out, have to move now, see you later

myguidingstar14:05:22

@wilkerlucio can you give me some example code for such metadata?

tony.kay14:05:34

@myguidingstar unfortunately, looking at the code, Iā€™m not sure params is properly implemented yet in set-query! šŸ˜ž

tony.kay14:05:25

the metadata on a query is just {:component Component}. Print it out using meta

tony.kay14:05:33

e.g. (meta (get-query Component))

tony.kay14:05:05

but that shouldnā€™t be necessary. Iā€™d consider this a case that probably needs a slight bit more API in Fulcro

tony.kay14:05:38

The best workaround I can think of for the moment is to namespace your parameters (e.g. :comments/offset) and just have the parent parse handler pass params down to all sub-parsing. Then children can pick up the params ā€œmeantā€ for them.

myguidingstar15:05:48

you mean parse handler in server side?

tony.kay15:05:24

just add them to the env

tony.kay16:05:30

@wilkerlucio Feeling appreciative of pathomā€¦such a good set of ideas

wilkerlucio16:05:27

@tony.kay glad to hear šŸ™‚ are you trying it on some project?

tony.kay16:05:31

have one Datomic structure that needs to morph into anotherā€¦connect looks like it will work nicely

wilkerlucio17:05:43

cool, I hand't had the chance to use with datomic actually, please let me know how the experience goes, I know the folks on Atlas CRM are doing pulls on datomic entities and using that as the output for chaining the things, I wonder if we could use the datomic entities directly (considering they interface like a map), and then would be a matter of saying which outputs you want to declare at any given time, but the entities also has the problem of maybe giving too much (because user can still ask for things that are not explicitly declared as the output), so I wonder how you are going about it

wilkerlucio19:05:02

@tony.kay one problem that I often hit when developing fulcro is when errors happen during mutations, most of the time the stack trace is useless, it only gets to the mutation start, but nothing inside, do you think we can make something to improve that?

currentoor20:05:16

@wilkerlucio do you mean on the backend?

tony.kay20:05:45

@wilkerlucio Yes, that is a problem. Using js/console.log directly should make it possible to fold open the back-trace, but then youā€™ve got console.log stuck in there. I have not had time to research a better way of reporting

wilkerlucio20:05:40

@currentoor on the front-end, the last relevant stack point is the entry of the mutation, but I often have to add custom console.logs in the middle to figure exactly what line broke

currentoor20:05:19

i use a macro that injects (js/console.log '<sym-name> sym) so often that i never relied on the stack trace to begin with šŸ˜…

currentoor20:05:44

but thatā€™s probably a sub-optimal workaround