re-frame

migalmoreno 2025-04-30T16:47:08.508899Z

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?

p-himik 2025-04-30T16:50:52.954389Z

To get the XY problem out of the way immediately - why do you want to avoid applying the controller?

migalmoreno 2025-04-30T18:05:42.443389Z

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.

p-himik 2025-04-30T18:10:42.070629Z

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.

wevrem 2025-04-30T18:21:56.545879Z

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))

p-himik 2025-04-30T18:23:28.860029Z

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.

migalmoreno 2025-04-30T21:21:23.749119Z

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

p-himik 2025-04-30T21:26:33.082609Z

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.

migalmoreno 2025-04-30T21:30:46.847559Z

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

Jeremy 2025-04-30T22:52:38.051369Z

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?

Kimo 2025-05-08T11:29:45.998349Z

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

Kimo 2025-05-08T11:34:44.622809Z

I'm also using flows in a big reactive dashboard.

Kimo 2025-05-08T11:35:17.716329Z

Also wondering if we can/should offer a way to debounce a flow.

p-himik 2025-05-08T11:35:38.816429Z

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.

p-himik 2025-05-08T11:37:52.730989Z

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.

Jeremy 2025-05-08T11:44:21.274119Z

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.

🙌 1
p-himik 2025-05-08T13:02:54.569629Z

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)))))

🙌 1
p-himik 2025-05-08T13:05:42.150159Z

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.

✨ 1
Jeremy 2025-05-01T12:29:50.742099Z

Alright thanks. I was just worried about there being lots of active flows

p-himik 2025-05-01T12:34:43.517019Z

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.

p-himik 2025-05-01T05:56:48.228889Z

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.

👍 1