Fork me on GitHub
#fulcro
<
2018-01-17
>
currentoor01:01:13

@tony.kay where was your book again?

mss14:01:09

is there a way to :target a load to have the return data merged into the root of the state db, as opposed to underneath the load key/some specified target key?

tony.kay17:01:17

@mss I don't understand. That is the default mode of load

tony.kay17:01:40

state is a map. You have to put things in there with a key

mss17:01:08

yep get that. let’s say my call to load for :user with a query of [:user/email :user/first-name] receives back a map like {:user/email "" :user/first-name "andy"}. when fulcro merges that load back into the state map it’ll look like {:user {:user/email "" :user/first-name "andy"}}. makes it slightly annoying to join throughout components, as I’m constantly doing [{[:user '_] [:user/email :user/first-name]}]. the alternative – and this is the crux of my question, I just don’t know how to accomplish it – is to merge all the properties returned by the :user load into the root of the state tree, so there’d be :user/email and :user/first-name keys at the root of the state tree, as opposed to nested under the :user key. that’d make querying for that returned data much easier, especially when its nested maps of properties (e.g. a user’s team member’s likes)

mss17:01:21

just spitballing. I don’t know if that’s recommended or even a good idea

tony.kay17:01:09

See the new docs on shared-fn

tony.kay17:01:56

a shared-fn of select-keys would be good for this

tony.kay17:01:25

you just need to make sure that a top-level refresh happens if such shared data changes...and in this case that's easy, so it's a very good fit.

tony.kay17:01:57

That support has been in Om Next and Fulcro since very early (2015), but not well-documented.

tony.kay17:01:05

note that shared-fn is on root props not the state database. Therefore anything you want to share from root has to be queried at root.

mss17:01:18

nice, really appreciate the help

currentoor20:01:19

hey @tony.kay what should I do if I find a typo in the book? is there a way to submit a pull request?

currentoor20:01:28

@wilkerlucio thanks, i didn’t realize it was in the main app

wilkerlucio20:01:59

no problem 🙂

currentoor21:01:00

I was bumping our app to 2.1. We had a homegrown routing component but I wanted to use the official Fulcro router. I got this example from the book working in my app http://book.fulcrologic.com/#_a_complete_ui_routing_example But for some reason switching to a new route (via clicking the links) does a double render. - First render from root is called with the current route - Second render from root is called with the new route This doesn’t seem like the way it should be, any ideas what could be wrong?

currentoor21:01:02

Also, was mutation logging turned off intentionally? I don’t see it anymore.

currentoor21:01:18

Clicking on current tab causes a single re-render too, that seems very strange.

tony.kay21:01:56

@wilkerlucio @currentoor it is all on mainline development now (the book)

tony.kay21:01:05

The source is DevelopersGuide.adoc in the root of fulcro

tony.kay21:01:16

the book branch is no longer used. I should delete it

wilkerlucio21:01:35

ups, sorry, I just saw the branch there and assume was been used

tony.kay21:01:05

@currentoor doing a transact will always render...what is your issue in particular?

tony.kay21:01:27

also, in the context of the book, I'm embedded an app in an app

tony.kay21:01:52

so there may be spurious rendering that is related to that

currentoor21:01:55

I added your example from the book into my AdStage app, and switching to a new route causes render to be called twice (once with the old route value and once with the new value)

currentoor21:01:30

I added that example into the root component of my app, should switching to a new route cause render to be called twice?

currentoor21:01:48

i guess i can verify with the example application

currentoor21:01:07

i just assumed that was not correct behavior

tony.kay21:01:21

ok, so it is possible there is a double call of render...but if the logic makes it into render, then some data changed

tony.kay21:01:57

there are two primary ways render gets queued: transact will queue things up, and swap!s on the state atom queue a root render/reconcile.

tony.kay21:01:29

scheduling a render does happen on an animation frame rate (throttled to 60fps), so a combo of transacts and swaps could trigger a dual render pretty easily.

currentoor21:01:26

oh so maybe this isn’t a problem then

tony.kay21:01:35

does it cause usability issues?

currentoor21:01:01

so a single transact and the swap!s inside it can trigger separate renders?

tony.kay21:01:10

so, it might be non-optimal. The rendering code rewrite was pretty invasive in 2.x. The fact that you're seeing old state then new is strange.

tony.kay21:01:35

the routing stuff is more complicated than that due to code splitting support.

tony.kay21:01:49

I'd have to look at the code to come up with explanations

tony.kay21:01:33

knowing what changed on the first render, compared to what changed on the second would be helpful

currentoor21:01:39

i’m just worried this will get expensive when i have a bunch of charts already rendered on the screen

tony.kay21:01:51

your render body will not execute unless something actually changed. So, double-render should not happen unless there is a data change.

tony.kay21:01:37

so, figuring out what changed in each of those steps is key to knowing why

tony.kay21:01:26

state1 (no routing) -> state2 (clicked for new route) -> state3 (route took effect)

currentoor21:01:24

i think only the diff is in that screenshot 😅

currentoor21:01:46

i should have circled it or something

tony.kay22:01:03

so, one diff should be one render no matter what

tony.kay22:01:26

do something else before routing to get a better tx history

currentoor22:01:31

> so, one diff should be one render no matter what then it might be something i’m setting up incorrectly, perhaps i should verify that this happens in the example application?

tony.kay22:01:58

shouldComponentUpdate returns false if the data hasn't changed. Where is your print?

tony.kay22:01:11

did you accidentally include it in more than one place?

currentoor22:01:27

the print is inside the render body of the root component

currentoor22:01:50

did not include more than once

tony.kay22:01:53

ah, that's a clue. force-root-render! is an exception: it forces the UI to update everything

tony.kay22:01:22

I don't think I force a root render in routing, but I'd have to look.

tony.kay22:01:00

which routing instruction are you using?

currentoor22:01:30

#(prim/transact! this `[(r/route-to {:handler :main})])

currentoor22:01:59

is that what you mean? i just copied the example from the book exactly

tony.kay22:01:44

so, as far as I can tell there should not be a double-render there

tony.kay22:01:11

Ah, there is a case where this can happen

tony.kay22:01:55

the new optimal render path has to do a forced update of a given component in case it was just a component-local state change. This would cause you to see the old props, then the new in rapid succession.

tony.kay22:01:29

The forced update only affects the component, as all child shouldComponentUpdate calls will return normally so that children won't render.

currentoor22:01:07

just verified and you’re right!

currentoor22:01:29

only the root component render body was being called, not it’s children

tony.kay22:01:37

At the moment, anything that is targeted at root will cause a double-root node render

tony.kay22:01:43

but should be cheap

tony.kay22:01:14

The new optimizations only work on components with idents. Root doesn't have an ident.

currentoor22:01:29

thanks for the help and explanation

tony.kay22:01:40

but shouldComponentUpdate is a pretty major optimization

tony.kay22:01:55

and the forced update does not re-run the query, which is the expensive part

tony.kay22:01:43

I haven't started recommending it, but using a placeholder root that does nothing but delegate to a node with an ident has a couple of benefits. One would be that it will be so lightweight that this could never matter; however, the other advantage is that nothing ends up in the root node but a single keyword (like :ROOT) and your tables.

currentoor22:01:22

can’t that be done by Fulcro?

tony.kay22:01:39

It could, but it would not be bw compatible

tony.kay22:01:56

since it would move things that you might be using link queries for

tony.kay22:01:27

and that is a main disadvantage...since nothing is in the root node, [ [:rootprop '_] ] is less useful

tony.kay22:01:04

so, I'm still on the fence about it being a "good idea"

tony.kay22:01:29

I like it, in that it makes it easier to find the stuff you're considering to be your root, since it is all in a map keyed at :ROOT

tony.kay22:01:24

however, as I pointed out to someone earlier today: shared-fn can be used to pull info out of the root props and into shared. That doesn't get broken...you just have to walk the props into :ROOT before pulling data into shared.

currentoor22:01:11

> since it would move things that you might be using link queries for if you’re using link queries, it shouldn’t matter if data moves up one more level right?

tony.kay22:01:40

ident-based queries, no...but root node is not longer root node, so it would make it harder

tony.kay22:01:47

you could no longer just pull an arbitrary root node value...you'd have to pull everything in :ROOT and then deal with that...and you would never want to join that with the actual root query, or you'd make a big mess

tony.kay22:01:36

so you wouldn't get denormalization without then writing some other component to query against the fake root

tony.kay22:01:12

[ {[:ROOT '_] (prim/get-query FakeComponentForPropINeed)}]

tony.kay22:01:56

[ [:ROOT '_] ] would just give you the ident of the new offset root node

currentoor22:01:56

I see, well I haven’t seen any super complicated roots. Feels natural to write simple roots that delegate to other more complicated components.

currentoor22:01:12

but I do have a smallish sample size 😅

tony.kay22:01:41

I'm tempted to make a "she said" joke there... 😜

currentoor22:01:33

careful now, don’t want fulcro to be crucified on twitter…

tony.kay22:01:53

any press is good press these days it seems

currentoor22:01:41

well we already know fulcro is so offensively good that it might cause someone to piss all over your expensive mechanical keyboard

tony.kay22:01:09

I don't think that is actually common knowledge

currentoor22:01:22

maybe it should go on the website

tony.kay22:01:43

urination stories probably aren't the best fit there 😉

tony.kay22:01:13

for anyone lurking on this convo...^^^ is a true story

wilkerlucio22:01:06

@currentoor I have this helper function that I like to use to make roots:

wilkerlucio22:01:08

(defn make-root [Root app-id]
  (fp/ui
    static fp/InitialAppState
    (initial-state [_ {::keys [root-state] :as params}]
      (merge
        {:fulcro.inspect.core/app-id app-id
         :ui/root                    (or (fp/get-initial-state Root (dissoc params ::root-state))
                                         {})}
        root-state))

    static fp/IQuery
    (query [_] [{:ui/root (fp/get-query Root)}])

    static css/CSS
    (local-rules [_] [])
    (include-children [_] [Root])

    Object
    (render [this]
      (let [{:ui/keys [root]} (fp/props this)
            factory (fp/factory Root)]
        (factory root)))))

wilkerlucio22:01:34

I think we talked about that one before @tony.kay, but I think we agreed it's not generic enough for general case, maybe

tony.kay22:01:44

And @wilkerlucio saves the conversation 🙂

tony.kay22:01:54

react-key isn't needed anymore

wilkerlucio22:01:40

well noted, I just bumped this project to fulcro 2.1 today 🙂

wilkerlucio22:01:59

yeah, also, the root div can also be removed

tony.kay22:01:03

there's a bug in union stuff that was just fixed

wilkerlucio22:01:43

snippet updated