Fork me on GitHub
#fulcro
<
2019-11-04
>
thheller09:11:03

so what is the best way to debug "render" problems? I have a bunch of messages streaming in from a websocket and often re-renders seem to miss. meaning that all the state is updated properly but the UI doesn't update. I can hit the render button in fulcro-inspect and it'll update the UI properly. as far as I can tell I'm telling the UI what to refresh via (fc/transact! app [] {:refresh vec-of-idents}) which I assumed would queue an actual render by maybe doesn't?

tony.kay16:11:59

@thheller Re-render should be done with the functions in the application ns, not as an empty tx. Also, you say you have messages streaming in…how are you integrating that state into the app itself?

thheller16:11:22

swapping directly into the atom

thheller16:11:55

too annoying to make a tx for everything 😛

tony.kay16:11:14

so, you don’t need normalization? Just hacking it into place?

thheller16:11:29

yeah not needed. all flat values

tony.kay16:11:21

then it is likely you’re not getting optimal refresh, but the answer remains the same: app ns refresh functions…schedule-render is recommended. You might switch to the keyframe renderer if you’re not using ident-based normalization

thheller16:11:47

I added some logging and whenever I queue the refresh it does actually render afterwards

tony.kay16:11:49

no need to take the analysis overhead if you’re just going to end up with a root render anyhow

thheller16:11:57

just somehow not with the data I swapped in before calling refresh

thheller16:11:18

I know exactly which ident I changed (most of the time it is just one)

thheller16:11:36

if I trigger a full app refresh for every single websocket message the whole thing never catches up

tony.kay16:11:14

schedule-render! should be a no-op if something is already scheduled

tony.kay16:11:39

it frame limits at 60fps using RAF

tony.kay16:11:57

and while scheduled it should ignore addl requests

tony.kay16:11:09

so, you do have normalized data…it’s just your messages are hitting known things that have no nested state…I think I misunderstood earlier

thheller16:11:10

oh yeah sorry. the data is normalized. I mostly just (swap! state update-in [::some id ::foo] merge some-data)

thheller16:11:51

or just (swap! state assoc-in [::some id ::foo] new-val)

tony.kay16:11:08

right, and have you tried schedule-render! ?

thheller16:11:29

no, I thought it doesn't watch the state-atom and won't know what changed?

thheller16:11:42

thats why I went for the fc/transact!?

tony.kay16:11:46

watch on the state atom has nothing to do with it

thheller16:11:57

I must avoid a full re-render

thheller16:11:06

tried that.

tony.kay16:11:15

So, tried what?

tony.kay16:11:19

specifically

tony.kay16:11:28

what do you consider a full re-render

thheller16:11:36

not using the ident-optimized render and just re-render everything

tony.kay16:11:46

but what function were you calling?

thheller16:11:00

keyframe-render/render! something

thheller16:11:07

set as :render! in opts

tony.kay16:11:18

ok, so, don’t override anything, and use schdule-render!

thheller16:11:34

so no fc/transact! just schedule?

tony.kay16:11:36

there is never a watch on the state aom

tony.kay16:11:04

the default render does an optimized diff on state based on on-screen components’ idents

thheller16:11:15

same problem

tony.kay16:11:13

what? never catches up?

thheller16:11:47

no, it somehow misses an update. If I press "re-render" in fulcro-inspect everything is fine

tony.kay16:11:00

ok, so re-render in inspect forces a refresh with no optimizations

tony.kay16:11:20

your problem is almost certainly that you are rendering something that is not represented in the component query

tony.kay16:11:36

refresh optimizations use the queries in the state diff

thheller17:11:37

hmm is this maybe the problem?

:query
   (fn []
     [::rid
      {::runtime [::runtime-info
                  ::supported-ops]}
      {::objects (fc/get-query ObjectListItem)}
      {::object (fc/get-query ObjectDetail)}
      ::nav-stack])}
joining without a component?

thheller17:11:35

::supported-ops not properly updating is one of the issues

tony.kay17:11:43

let me think….I don’t know how the indexer would respond to that

tony.kay17:11:18

The conversion to AST would be missing a component (and ident)…but there is nothing that would mount there (supported ops is rendered in that component I assume)

tony.kay17:11:44

In general if you have a non-normalized chunk of opaque data you don’t represent it in the query (since it will just be extra db->tree overhead)

thheller17:11:22

hmm let me try just adding a component with ident for this. maybe that fixes it

tony.kay17:11:21

do you have a natural ident for it?

thheller17:11:29

:query
   (fn []
     [::rid
      {::runtime (fc/get-query Runtime)}
      {::objects (fc/get-query ObjectListItem)}
      {::object (fc/get-query ObjectDetail)}
      ::nav-stack])}

tony.kay17:11:31

I would personally just drop the join

thheller17:11:34

doesn't fix it

tony.kay17:11:39

[::rid ::runtime …]

tony.kay17:11:37

but wait…you did that really quickly…Runtime is already a component?

thheller17:11:10

(defsc Runtime [this {::keys [rid] :as props}]
  {:ident
   (fn []
     [::rid rid])

   :query
   (fn []
     [::rid
      ::runtime-info
      ::supported-ops])}

  (html/div "runtime" (pr-str props)))

(def ui-runtime (fc/factory Runtime {:keyfn ::rid}))

thheller17:11:23

not used anymore but was still in the code. just adjusted the query

tony.kay17:11:52

and you changed render to render with its factory?

thheller17:11:20

no. I need to look at the ::supported-ops to decide what to render

tony.kay17:11:29

that’s why you don’t get refresh

tony.kay17:11:53

defsc hacks into componentDidMount to index components that are on-screen

tony.kay17:11:07

if you don’t actually mount a component there, then nothing ends up in index

thheller17:11:30

hmm thats why I didn't join 😛

thheller17:11:38

but yeah that makes sense

tony.kay17:11:40

so, don’t normalize either

thheller17:11:58

how do I get the data then?

tony.kay17:11:25

I’m saying that the runtime stuff can just be an opaque map at the ::runtime key on that parent

thheller17:11:48

I have a "page" which has {::runtime [::rid 123]} (in the data)

tony.kay17:11:57

as an ident

tony.kay17:11:08

that has to be done as a component for refresh

tony.kay17:11:20

BUT, you can just put a map there, and don’t write a join

tony.kay17:11:33

that entire value will then show up in the component

thheller17:11:55

I don't follow. the data must remain on the runtime itself?

tony.kay17:11:36

OK, so the diff on the parent checks to see if the IDENT of the child changed. It is expecting the child itself to manage its own refresh…parent (when normalized) only cares if the identity of the child changed

tony.kay17:11:06

If you make the child a “scalar” (not a join in the query), then the parent will care about the value (which in your case would be a nested map in app state)

tony.kay17:11:17

But of course you lose normalization convenience if you do that

tony.kay17:11:31

so if you need normalization, then we need to talk about how to fix your overall approach

thheller17:11:50

how do I query ... I just don't follow because I don't know how to query a value from another ident without a join

tony.kay17:11:05

the point is it would NOT be an ident

thheller17:11:08

I have a runtime-page and a runtime

tony.kay17:11:09

it would be a map

tony.kay17:11:22

ok, so you’re indicating that you need normalization

thheller17:11:40

runtime-page has {::runtime [::rid 123]}

thheller17:11:48

and a bunch of relevant stuff for the page itself

tony.kay17:11:10

and the data in [::rid 123] is used in many places on screen or in db?

thheller17:11:13

::rid is runtime-id

tony.kay17:11:43

ok, then the answer is “yes, you need normalization”…in that case you also need to use proper join query and component factories

thheller17:11:14

ok so instead of runtime-page working with the data directly I need to pull those parts out

tony.kay17:11:58

right, and I suspect you might have logic that would benefit from a union query…but just guessing

thheller17:11:37

nah probably just lazy. its an overview page showing some stuff about the runtime and then all the other data it reads

thheller17:11:51

didn't want an extra component for a div basically

thheller17:11:09

but yeah makes sense that it doesn't update properly then

tony.kay17:11:18

There are other options…the runtime has a spot to put “additional refreshes” for the scheduled render…adding the ident of the parent there should do the trick. That’s essentially what you were trying to do with your transact! trick..but I think an empty tx just ends up being a no-op. No queue processing happens, so nothing gets scheduled.

tony.kay17:11:09

you could try adding options to schedule-render!…I think that would work: (app/schedule-render! app {:refresh [ident-of-parent]})

tony.kay17:11:33

There is also an :only-refresh option

tony.kay17:11:22

both of those options only work with the default renderer, and I’ve not tested the latter heavily…but I see no reason why that would not work. Sounds like you’re possibly a good use-case for the :only-refresh…it circumvents the diff analysis and just schedules a targeted refresh.

thheller17:11:25

ok one problem seems to be fixed. can't reproduce anymore

thheller17:11:33

should now the able to fix the other too

tony.kay17:11:42

I should have documented that those options on transact! are passed through to schedule-render! Welcome 🙂

thheller17:11:23

I'll try those options

tony.kay17:11:32

Just to be clear, @thheller, :refresh means “do the full refresh analysis, and include this extra stuff even if it doesn’t look dirty”, and :only-refresh completely skips the state diff, so is faster when you know what you want to refresh.

tony.kay17:11:57

Both support keywords (which looks up idents via query index) and idents.

thheller17:11:16

:only-refresh is likely what I want then

tony.kay17:11:39

Oh, and in the case where you’re not using a factory for that child, the ident/keyword you want to refresh is the one on the parent.

tony.kay17:11:03

(i.e. :only-refresh [::runtime])

thheller17:11:52

@tony.kay one more thing. I have split up the representation of one ident into 3 smaller ones. basically because the body changes a lot while header/footer don't. premature optimization I know ...

thheller17:11:55

(when object
        (html/fragment
          (ui-object-header object)
          (ui-object-detail object)
          (ui-object-footer object)))

thheller17:11:33

but now I can't figure out how to make one "object" component that then does the split?

thheller17:11:25

or maybe its just something else but sometimes this display doesn't update either. although those components always have [::oid 123] as their ident

thheller17:11:44

I'll probably jujst make 3 regular react components to do the split

thheller17:11:51

no point it doing it this way

tony.kay17:11:40

so, if they share an ident, the renderer will trigger refresh on them all anyway when the data changes…if their queries differ, then s.c.update might actually short-circuit

tony.kay17:11:13

I would make one component, and you could push the header/footer out to comps without a query/ident just to get the s.c.u. optimization

tony.kay17:11:40

(defsc Header [this props] (dom/h4 ...)) (no options at all)

👍 4
mruzekw18:11:19

There’s no reason a Fulcro-compatible backend couldn’t be written in CLJS, right?

kszabo18:11:04

yes, completely agnostic, Pathom already works in CLJS. You could have a ‘server’ locally in your browser right next to the client (this is what the Fulcro book does)

mruzekw18:11:23

Nice! Very happy to hear. Thanks