Fork me on GitHub
#fulcro
<
2018-06-19
>
levitanong02:06:53

@tony.kay Seems to work fine so far!

OliverM14:06:11

Do load post-mutations always wait until the load completes before running? In the following I’m getting the console log statement before the load completes…

(df/load this :login/user shared/StaffData
                        {:params {:email email :password password}
                         :post-mutation `login-complete
                         :refresh [:login/user]})

(m/defmutation login-complete [_]
  (action [{:keys [state] :as env}]
          (.log js/console "Completing login...")
          (let [user-name (-> @state (get :login/user) second)
                user (get-in @state [:staff/by-name user-name])]
            (when user (r/route-to-impl! env {:handler :PAGE/app})))))
(the load is part of a larger component on-click handler)

claudiu14:06:44

@oliver.mooney They should. Tried on 2.5.7 and the latest 2.5.10-snapshot I'm getting the console.log after the load finishes (added a 5 second sleep to thread)

claudiu14:06:21

there is this in the docs "You side-effect. Your mutation will be called at least two times so this is a bad idea. Side effects should be wrapped in the action." but what you pasted seems right

OliverM14:06:19

@claudiu thanks for checking. I’m on 2.5.7. My console log from a fresh reload looks like:

Installing Fulcro Inspect
Turning logging to :all (in adaptdb.development-preload)
Installing CLJS DevTools 0.9.10 and enabling features :formatters :hints :async
browser.cljs:27 shadow-cljs: WebSocket connected!
browser.cljs:27 shadow-cljs: REPL init successful
root.cljc:21 Completing login...
goog.net.xhrio.js:627 XHR finished loading: POST "".

claudiu15:06:50

is it a big difference between the console-log and the response ?

tony.kay15:06:59

It is run after response. How do you know the load isn’t complete when you’re seeing logging?

OliverM15:06:40

@tony.kay ah so processing of the load happens in parallel with the post-mutation? what’s the best way to wait until the load is complete in that case?

tony.kay15:06:03

what part of what I wrote said “parallel”???

tony.kay15:06:32

post mutation is meant ONLY for morphing just-loaded data…it is the reason it exists. period. 🙂

OliverM15:06:30

@tony.kay hmmm I’m not getting it. I’m modeling this post-mutation on the example given in the fulcro template app here: https://github.com/fulcrologic/fulcro-template/blob/master/src/main/fulcro_template/api/mutations.cljs

OliverM15:06:15

That login-complete mutation has routing on successful login via the LoginPage component here: https://github.com/fulcrologic/fulcro-template/blob/master/src/main/fulcro_template/ui/login.cljc

claudiu15:06:58

post mutation is called after the data is loaded, since it's build so that you can move the newly loaded data to other places in your app state. If it were to load before, then you would not have access to what you loaded from the server.

claudiu15:06:17

really strange that you're getting that console.log before. Can you try to log user, maybe it's being called from somewhere else or something 🙂 ?

tony.kay15:06:45

@oliver.mooney how are you doing networking? I.e. how are you getting log messages out of the network?

tony.kay15:06:14

I suspect you wrote you own networking, and are calling ok too soon

tony.kay15:06:20

or multiple times?

OliverM15:06:36

@tony.kay only using networking provided as part of the fulcro framework, nothing custom

OliverM15:06:54

fulcro-server on the backend

tony.kay15:06:10

goog.net.xhrio.js:627 XHR finished loading: POST "".

OliverM15:06:39

My handler reads

(defquery-root :login/user
  "Supply current user details (logging the user in if not already logged in). Ported from fulcro-template example."
  (value [{:keys [db request]} {:keys [email password]}]
    (if (and email password) ;; credentials presented; attempt login
      (validate-user db request email password)
      (let [current-user (-> request :session :staff)]
        (timbre/info "Currently logged-in user: " current-user)
        current-user))))

tony.kay15:06:25

So, do this: Log app state from within your client mutation to see if the loaded data is there…console messages can also appear out of order due to async nature of XhrIO

tony.kay15:06:43

If you’re using the stock stuff, then post mutations will run after load is complete

OliverM15:06:10

The state is there with the expected data from the load! Good point about the console messages appearing out of order, I should have thought of that. But now I don’t know why my post-mutation routing isn’t firing. If I press the login button twice (ie. so the second time the data has been loaded and normalised) the post-mutation works and the screen re-routes to the logged-in screen

OliverM15:06:52

So I think you and @claudiu are trying to explain something to me about post-mutation that I’m just not seeing 😅

OliverM15:06:43

I get that it’s for reshaping data, but it seems to allow for the routing change given the fulcro-template example. And when I do something else and the code recompiles (in dev config) the screen changes to the logged-in screen without my doing anything new to the state, so the router is responding to the route-to-impl! at that point too

OliverM15:06:09

Is there a :refresh vector I should be adding?

claudiu15:06:49

can't remember exactly if there's a gotcha 🙂 with refresh & post-mutation updates to state. 🙂

🙂 4
claudiu15:06:49

is the app state the same , but the screen only changes the second time you click ?

claudiu15:06:57

can you try instead of :refresh [:login/user] something from the root component or is it there ? know I've had some similar issue but can't really remember.

tony.kay15:06:23

Refresh is what you need. Post mutations have no attachment to UI, so you do need to say what it is changing

tony.kay15:06:51

if you’re routing, then refresh the key that you join your router on

OliverM15:06:38

Ah that makes sense, I didn’t get that post-mutation updates don’t trigger refreshes of impacted part of the state tree. Putting :router into the :refresh vector works!

OliverM15:06:42

Thanks for your help guys

OliverM15:06:52

Sorry I was slow on the uptake 😅

OliverM15:06:38

Incidentally why doesn’t that work when I add the refresh as part of the mutation, rather than part of the load? e.g. before I did have

(m/defmutation login-complete [_]
  (action [{:keys [state] :as env}]
          (let [user-name (-> @state (get :login/user) second)
                user (get-in @state [:staff/by-name user-name])]
            (.log js/console user)
            (when user (r/route-to-impl! env {:handler :PAGE/app}))))
  (refresh [env] [:router]))

OliverM15:06:53

But that didn’t trigger the refresh. Adding it to the df/load call works though

tony.kay15:06:55

I made the same mistake recently…I’m toying with possible changes to rendering model. Fiddling with targeted refresh on post-mutations probably isn’t helpful…probably should just force a root render by default. There are so many optimizations already that this is probably just wasting ppls time

tony.kay15:06:30

Post mutations are processed in a separate part of the stack. I would consider that a bug (listing refresh in mutation should “just work”)

tony.kay15:06:18

But, again I think I’d prefer just doing a root render on post mutations. shouldComponentUpdate is already a huge optimization. There’s no reason to limit post mutations to the transacting (load-running) component

👍 4
OliverM15:06:22

@tony.kay want an issue raised on the possible bug with the defmutation’s refresh? I can add the bits of code etc above

tony.kay15:06:38

I’d love benchmarks on the :keyframe render mode on real apps…I think making it the default might just be the “right answer”

tony.kay15:06:51

@oliver.mooney I’ll get it…thanks 🙂

👍 4
OliverM15:06:23

But just firing a root render would be much simpler as a mental model too, especially if shouldComponentUpdate already has your back

tony.kay15:06:12

I think I just decided: it should work as-is, but you can use :keyframe render mode if you’d like simple model

tony.kay15:06:50

the problem with doing root render is if you have some real-time complex UI that does a lot of networking to update some part of the UI…might lead to too much overhead, and there’d be no way to optimize it

tony.kay15:06:57

e.g. “going back” would not be an option

claudiu15:06:02

If i'm doing scheen change routing. Triggering with component and follow-on reads is revommended, or theres a chance that using the reconciler could be just as fast ?

tony.kay15:06:38

I’m not “recommending” either…since your app will have specific performance issues.

tony.kay15:06:57

I think a lot of UIs can just use :keyframe rendering mode and forget about follow-on reads altogether

tony.kay15:06:19

much simpler code, and shouldComponentUpdate will make 90% of apps plenty fast enough

tony.kay15:06:24

esp in adv compile mode

tony.kay15:06:44

the follow-on reads in normal rendering mode make it “as fast as possible”

tony.kay15:06:28

If I get around to a book rewrite I’d probably make :keyframe the default, and the follow-on read stuff with the current rendering mode a “and if you have performance problems, here’s what you can do” thing

tony.kay15:06:11

The advantage of it being “the default” is that it gets tested a lot 🙂 And that optimization covers a lot of the complexity in the code.

tony.kay15:06:55

The problem with a root render isn’t just the rendering overhead, though

tony.kay15:06:00

it has to run a root query

tony.kay15:06:12

and that is often 90% of the CPU overhead in a root render

tony.kay15:06:32

So the targeted update gets you past that overhead as well

claudiu15:06:53

Ohhh :) that makes sense.

tony.kay15:06:18

But if it is just a forms-based app with proper union queries for routes, then I bet root overhead is low

OliverM15:06:39

@tony.kay that’s my use-case for this app - I might just go for :keyframe now in my fulcro-beginner hat

claudiu15:06:27

For loads when you enter a screen, is there a recommended place to put them. I used componentwilmount in the past. Now building my rounting to check for a protocol method on-enter. Curious if there are better approaches to this, Keechma framework has start/stop on in its routing layer.

tony.kay15:06:15

I put my ensure logic in a mutation and add the routing to that same mutation.

tony.kay15:06:58

and certainly that could be built into a “routing layer” library

tony.kay16:06:21

I think the protocol approach has some appeal, for sure

tony.kay16:06:57

on-enter and lifecycle are a bit too side-event-like for my taste

claudiu16:06:32

Still thinking aboit it. But like that in my component I have everything (query, ident, what it loads, caching logic) I can also call it from ssr. Will see how it goes once I get some more pages and usecases :)

claudiu16:06:30

For dynamicrouter. Is it designed to work more with :route :singleton. Tried a bit to get it working with :route :param/somth but seems like I have to install a new route for each param. Did I get that wrong ?

tony.kay16:06:02

The dynamic router is all about code splitting…you doing code splitting?

tony.kay16:06:09

otherwise it works a lot like defrouter

claudiu16:06:10

Yep. Although im doing it as on modules(grouped more routes) not routes so basically bypassing its load :)

tony.kay16:06:12

so, with dynamic router you do have to call install-route for each route

tony.kay16:06:27

(that is in-core)

tony.kay16:06:45

so install for each route that is pre-loaded, and then when a module loads install for each route in that module

tony.kay16:06:52

is that what you’re asking?

claudiu16:06:41

I noticed it adds a marker when installing. But if my route is :article :param/id . Then it looks like I have to load that in appstate, install it for that ident and then trigger the route change. Noticed the examples are with :article :singleton

tony.kay16:06:31

I don’t remember coding parameters with respect to dynamic router.

tony.kay16:06:14

how would the dynamic router know how to load your special identified instance of an article? It is meant for module loading, not instance loading

tony.kay16:06:37

so yes, it is targeted at one-off kinds of screens

tony.kay16:06:48

the sub-screens can use something parameterized with normal routing

claudiu16:06:00

ahh true, makes sense. Think I was a bit confused because I only use dynamicrouter for changing screens. Routes have namespace and i make sure the module is loaded before calling (using shadow-cljs/loader).

claudiu16:06:23

thank you 🙂

OliverM16:06:02

@tony.kay would a layered approach be possible where :key-frame rendering is the default, but you can attach :refresh vectors on components as a performance optimisation, which the renderer would apply only to those components?

tony.kay16:06:31

too much swiss-army knife to the internals I think

tony.kay16:06:52

and the refresh does not belong on components

tony.kay16:06:01

it has to do with data changes, not direct component rerendering

OliverM16:06:03

sorry yes meant load/mutations

OliverM16:06:19

but I see what you mean when I think about it in that context

tony.kay16:06:30

Then again, I think that is sort of what I’m saying: root refresh as sort of a default, and opt-in to the optimization

tony.kay16:06:41

so, perhaps what you’re suggesting is reasonable

tony.kay16:06:05

If the mutation declares a refresh, limit the refresh to things that query those data items; otherwise just render the whole thing

OliverM16:06:55

yep, and the decision has a much lesser impact on the codebase, rather than having to work through it to find all cases where a refresh is needed once you switch away from :key-frame

tony.kay16:06:06

There is a convenience to the current model: the this of the transact is always locally refreshed

tony.kay16:06:12

you’d lose that, and would have to go about declaring it on a lot of mutations…but I guess that isn’t so bad. If it is performing ok, who cares?

👍 8
tony.kay16:06:47

Another possible down-side has to do with PR…at the moment the “default” rendering story is quite fast. Doing this would make it probably one of the slower UI libs out there (even though “slow” might be plenty fast enough)

tony.kay16:06:23

lib X gets 60fps, Y only gets 25…that kind of silliness

OliverM16:06:56

FWIW if I knew a framework saved me developer time at a cost of 10fps (say) I’d be all over it - it’s my most precious resource

OliverM17:06:15

But I’m not doing games in the browser or anything 🙂

tony.kay17:06:52

That’s a good one 🙂

sundarj17:06:07

When 500% Faster is Garbage by the same author is a fun one also

👍 4
liesbeth18:06:40

Hey guys, I’m experiencing some difficulty with a form, and I’m hoping someone can help me. I’ve pretty close to copied this example: http://book.fulcrologic.com/#_loading_or_creating_something_new and it is going pretty well. I have a form with subforms and the state seems to be updating fine on typing etc, but when I send the diff to the server, all values are empty strings or arrays. The strange thing is: when I fill in the form and trigger a reload via figwheel by changing something, it works! So my hypothesis is that for the transaction that is linked to the button onClick action (

:onClick #(prim/transact! this `[(submit-poll {:id ~(:poll/id poll-form)
                                                                :diff ~(fs/dirty-fields poll-form false)})])
), the syntax quoted part is ‘filled in’ when this is rendered, and I need to trigger a re-render of the component that submit button is in order to have the right diff sent to my server. But I don’t know how to make that component realize the form was updated. At first I had the submit button in the form component itself, but that gave problems that the last updated field was not sent to the server. I’ve now moved the button outside of the form, exactly as in the example, but this gave me the problem as described. I’m suspecting it is something very small I am missing, but I’ve been staring blind at it for a day now 😉. Tips are appreciated! Snippet below:

tony.kay19:06:06

@liesbeth I think you’re running into re-render problems…you see the functions in PollsHome are doing the submit, but the form changes are going on in the Form, which is rendering by itself

tony.kay19:06:13

(rendering optimization)

tony.kay19:06:39

So, the data in your Home component is out of date when you submit, because it closed over the empty form when it rendered

tony.kay19:06:30

The submit buttons should either be “on” the form, or you should force updates of the parent in your mutations on the form so it closes over the new state as it changes

tony.kay19:06:39

Second question today that has to do with rendering optimizations that would be fixed using :keyframe rendering…

tony.kay19:06:12

Fulcro lein template updated. The new template sets keyframe rendering in the client, which might be better for beginners.

👍 8
tony.kay20:06:39

@oliver.mooney Try 2.5.10-SNAPSHOT on the declared post mutation refresh problem. I think that fixes it

tony.kay20:06:01

i.e. you should be able to declare your refresh on the post-mutation now instead of on the load

OliverM20:06:32

@tony.kay just tested it, works great

tony.kay20:06:10

cool, thanks

tony.kay20:06:52

Fulcro 2.5.10 on clojars. Fixes bugs we’ve seen in-channel the last day or two

parrot 16
claudiu21:06:17

Been on my mind for a while. Is the reason why aborting a load the state is replaced with nil internal or because its a better practice ?

wilkerlucio21:06:26

fulcro inspect extension was updated (`0.0.8`), if you were in 0.0.7 just update on chrome (or wait for chrome to update itself), this brings database search feature, thanks to @tony.kay!

🙂 8
claudiu21:06:00

Really cool. Fulcro-inspect is trully a life saver. Thank you

tony.kay21:06:49

@claudiu aborting a load should do nothing to state if you’re not using a marker.

tony.kay21:06:58

If it does, I’d consider it a bug

tony.kay21:06:32

if you’re using a load marker, then the state has already been overwritten

claudiu21:06:12

@tony.kay read in the docs and never actually tested :(

claudiu21:06:24

Aborting an active request stops the network transaction and acts as if the requested data resulted in an empty map from the server (so that load merge will overwrite the target with nothing). If this is a problem then target the load to a placeholder location and use a post-mutation to move it when load completes

tony.kay22:06:37

ah, thanks for the reminder

tony.kay22:06:17

@claudiu So, that’s the built-in behavior of a Fulcro http remote…but the abort call is abstract, so you could easily change it. In fact, we could add some kind of option to http-remote and let you choose. It could call the error-routing instead, which would then have the effect of doing nothing if there was no fallback or marker.

tony.kay22:06:39

as it stands abort calls the ok handler with an empty map, as described in the docs