Does anyone know how to avoid applying Reitit's start controller on shadow cljs' hot reload? I'm using something similar to this example https://github.com/metosin/reitit/blob/master/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs to load API data when accessing a route, but making changes to the views components on development causes the start controller to be re-triggered each time (so I just comment out the apply-controllers to prevent it). On a side note, has anyone managed to implement client-side caching using Reitit and https://github.com/superstructor/re-frame-fetch-fx?
To get the XY problem out of the way immediately - why do you want to avoid applying the controller?
Because it re-triggers an API call which sets the page loading state to true, showing a spinner and essentially wiping out the state of the components with previously loaded data.
In a similar situation, I just made all navigation idempotent.
If the data is already there, do nothing. If something is not there - show the spinner, load the data. Then reinitializing the router stops being a problem.
If you not only store the current data but also the data for the previous page, navigating back and forth would also become much smoother.
But if you're still certain you'd rather have your router be initialized exactly once regardless of hot code reloading, just split init into two functions - main and render! or something like that. And call render! from main and also when shadow-cljs loads stuff. That way, if your router initialization is in main itself, it won't be called on code reloading.
You're following the example, and that's okay, but I think that it is odd to call (init) at the bottom of the file. A more standard approach is to let shadow know about your init function via the settings in shadow-cljs.edn, something like:
{...
:build {:app {...
:modules {:home {:init-fn }}
...}}}
You can also use the tag :dev/after-load to help manage hot reload.
(ns
(:require
[re-frame.core :as rf]
[reagent.dom :as dom])
(defn render []
(dom/render [app] (.getElementById js/document "app")))
(defn ^:dev/after-load clear-cache-and-render! []
(rf/clear-subscription-cache!)
(render))
(defn init []
... dev setup, initialize routes, start the router...
(render)) Oh, I didn't even notice that it was called there immediately after definition. Yeah, one shouldn't do side-effects during namespace loading in general.
About making navigation idempotent, do you have any examples? In my case I have a 'search' route which has an API fx to load the data and I need it to re-trigger the fx every time there's new API data, which is why I mentioned caching, since if there are no changes I wouldn't want the fx calls (dispatched in the start controller) to be called and it might also solve the hot reload issue.
About the second one, I'm not sure if the issue is at router initialization tbh but moreso on the 'on-navigate' router callback for reitit.frontend.easy/start! which 'watches' on route changes and based on that applies the controllers. On a closer look, I think my issue is similar to this https://github.com/metosin/reitit/issues/383 as a change in the view component associated with the route seems to be re-evaluating the controller and thus causing the route change and in turn the start controller to be triggered
No examples that I know of unfortunately. But it's just as simple as "don't do anything you don't have to do", and fetching the data when it's already there, regardless of whether or not the router is being reinitialized, is something you don't have to do. Regarding the linked issue - note something that Deraen said: > Usually, when developing app I want the controller to be called if I changed it. So if your view changes, then yes, of course something that depends on that view should also be refreshed. That means that making controller refresh idempotent makes even more sense.
Gotcha, I'll see if I can make that work with what I got, thank you. Otherwise I'll ask in #reitit if someone's come across a similar thing
Hi everyone, I'm working on a very reactive dashboard; lots of components on screen and updates per second (would throttle to about 10/s) I've read most of the reframe guide and think flows would simplify things for me. However, I'm worried about the performance. My understanding about the implementation, which I got from the flow intro page, leads me to this it's sub-optimal (i.e. lots of destructuring and equality checks for all flows per event). I'm not sure if that's how it is actually implemented or if it was worded that way so the reader can easily understand it. My question is as to whether flows can be work for my use-case. is the performance hit discernible?
Yes, currently we use map destructuring, which can add up. I'm open to suggestions. Should we use positional arguments, like in a subscription's signal function? I'm hesitant to leave behind the clarity of named arguments. Is there a way for the api to offer both? How else could we improve performance? Most of the impl. is here: https://github.com/day8/re-frame/blob/e6f03560ae9da8b27303efcd616c9f28f04de9e9/src/re_frame/flow/alpha.cljc#L141
I'm also using flows in a big reactive dashboard.
Also wondering if we can/should offer a way to debounce a flow.
Has it actually been shown that destructuring makes a sizable contribution to run time? I have an app with roughly 10k active React instances, each is a Reagent component that uses destructuring - it works just fine on my setups.
An ability to debounce a flow sounds potentially useful - I already have a few global interceptors that use debouncing that could potentially be replaced with flows.
I'd try flows as it is in my dashboard and report on it here when done. I think debouncing would be great to have regardless.
Just did a test on unwrapped ("linear") args, vector destructuring, and map destructuring, all with 10 args.
Mean and std over 10 iterations. Built with shadow-cljs 3.0.5 with default build settings (some things might change if a different :output-feature-set is chosen).
Firefox 138.0.1:
outer-iter calls-per-new linear-mean linear-std vector-mean vector-std map-mean map-std
100000 1 1.20 1.66 34.50 11.17 229.90 3.73
10000 10 0.70 0.64 28.30 0.64 86.20 0.40
1000 100 0.80 0.75 28.00 0.77 72.80 0.75
100 1000 0.90 0.30 27.80 0.40 70.80 0.40
10 10000 0.40 0.49 28.40 0.49 70.30 0.46
1 100000 0.70 0.46 28.00 0.63 70.50 0.67
Chrome 136.0.7103.59:
outer-iter calls-per-new linear-mean linear-std vector-mean vector-std map-mean map-std
100000 1 0.96 1.78 10.14 9.55 106.18 6.81
10000 10 0.36 0.05 6.57 0.35 35.08 0.22
1000 100 0.37 0.05 6.18 0.11 27.48 0.10
100 1000 0.36 0.05 6.23 0.06 26.88 0.26
10 10000 0.38 0.04 6.22 0.06 26.92 0.07
1 100000 0.33 0.05 6.25 0.07 26.83 0.17
Pseudocode to explain what the first two columns mean:
(bench
(dotimes [_ outer-iter]
(let [arg (make-arg)]
(dotimes [_ calls-per-new]
(do-stuff arg)))))While there is a difference, I don't think it makes sense to make the code of flows worse given that it's probably extremely atypical to have thousands of flows, let alone tens or even hundreds of thousands, where most flows are considerably faster than map/vector destructuring. At that point, the app's code itself will most likely choke over what it does beside the flows.
Alright thanks. I was just worried about there being lots of active flows
I'm yet to use them, because I already have a somewhat similar workflow that predates them. But it seems to me that there should be rarely a reason to have many of them. The vast majority of the time you will still use regular subscriptions that lean on other subscriptions or on what some events have explicitly put into app-db.
10/s is a low number, don't worry about it. If things become slow - chances are it will be due to your app's code and not due to re-frame.