reagent

Jérémie Pelletier 2024-02-26T14:37:55.449459Z

is there a reason for reagent to enforce component state to be maps?

Jérémie Pelletier 2024-02-26T14:38:32.460849Z

I have a CodeMirror instance I want to set as the state, without the indirection of wrapping it into a map, but this throws an assert error

p-himik 2024-02-26T14:40:38.367169Z

What do you mean by "component state"?

p-himik 2024-02-26T14:40:43.392169Z

And what is the assertion error?

Jérémie Pelletier 2024-02-26T14:40:47.246929Z

rc/set-state, rc/state calls

Jérémie Pelletier 2024-02-26T14:41:09.211069Z

because reagent itself hooks into the react state, so can't use that one directly

Jérémie Pelletier 2024-02-26T14:41:37.195519Z

working around this by using a separate JS property, ie local but wondering if there's a better way

Jérémie Pelletier 2024-02-26T14:42:14.877639Z

the whole atom+map indirection seems overkill when neither are truly needed

p-himik 2024-02-26T14:46:27.627479Z

Heh, I've never even used that functionality, completely forgot about it. > reagent itself hooks into the react state, so can't use that one directly Not sure what you mean. Why not just use a plain atom, if you need for it to not be a part of the React life cycle? Or a ratom, if it must be a part of that. > the whole atom+map indirection seems overkill when neither are truly needed That's just the convention Reagent chose, and it's the only reasonable one that works when you need set-state to actually merge the state and not replace it altogether. I wouldn't call it an overkill - how much harm can a one map do? And an atom is needed in order to make the component react to state changes, since it's actually a ratom.

Jérémie Pelletier 2024-02-26T14:48:27.711589Z

ah in this case its not used for reactions, but used with a create-class component with lifecycle methods; had performance issues at scale last time I naively put atoms everywhere

p-himik 2024-02-26T14:49:16.552079Z

You can put a regular atom in a let right outside the call to create-class. Or react/createRef, if it's a proper ref. Or a JS object, whatever you prefer.

Jérémie Pelletier 2024-02-26T14:49:40.020769Z

ah didnt consider wrapping the whole create-class around a let, thanks!

Jérémie Pelletier 2024-02-26T14:49:48.599759Z

thought that was form-2 only

p-himik 2024-02-26T14:51:02.717529Z

That's not for any specific kind of components, it just makes the functions close over some values that you chose. It can even be around a form-1 component, although those values will remain static after being initialized at the ns loading time.

p-himik 2024-02-26T14:51:35.334769Z

(Unless you crate form-1 components as lambdas, of course).

Jérémie Pelletier 2024-02-26T14:52:11.517049Z

yeah also trying to avoid long-lived lambdas, they tend to be a nuisance when live editing code 🙂

Jérémie Pelletier 2024-02-26T14:52:50.171629Z

or if I do, i made a wrapper dev macro to wrap the call around another lambda, so the original function can still be redefined from cider

p-himik 2024-02-26T14:54:24.929289Z

Sounds complicated and I have no clue what it all means. :D On the frontend I use the automatic hot reloading by shadow-cljs, I don't have to think about long-term anything, apart from the state of course.

Jérémie Pelletier 2024-02-26T14:55:29.950299Z

ah, well imagine you have references to functions inside that state, ie a map from keybindings to command functions, just reloading code wont cause the state to automatically point to the new definitions

Jérémie Pelletier 2024-02-26T14:56:57.198029Z

or registered event handlers, this is a more common case; ie mediaDevices.ondevicechange

p-himik 2024-02-26T15:02:55.963979Z

I see. I very, very rarely encounter those scenarios myself. Possible factors for why: • Such a function is going to be re-set on a hot code reload (because loading a particular ns sets the value) • Not the function itself is referenced but some indirection (like e.g. event IDs in re-frame) • React does it for me > registered event handlers An event listener can be not only a function but also an object that wraps a function. E.g. I do this to listen to keyboard events globally:

(defonce -listener (let [listener #js {:handleEvent (fn [_])}
                         opts #js {:capture true}]
                     (js/addEventListener "keydown" listener opts)
                     (js/addEventListener "keyup" listener opts)
                     listener))

(defn stop-recording! []
  (set! -listener -handleEvent (fn [_])))

;; For hot reload.
(stop-recording!)
And later in the code I can (set! -listener -handleEvent (fn [evt] ...)).

p-himik 2024-02-26T15:04:20.458329Z

Of course, it's an ad-hoc solution. But the problem is also kinda ad-hoc. Definitely not worth it to switch from the automatic hot reloading to any manual approach, at least for me (although on the backend I use the "reloaded" workflow with a manual trigger).

Jérémie Pelletier 2024-02-26T15:05:04.772239Z

ah yeah, trying to avoid having to add manual steps to iterations, I just do (js/addEventListener "keydown" (cb some-function)) then cider-eval-last-sexp over (defn some-function [e] ...) makes it live instantly; saving the file and waiting for the whole reload flow is ~2-5 seconds on this project

Jérémie Pelletier 2024-02-26T15:06:03.256059Z

where cb is that macro I made, which is identity in optimized builds, and wraps the call in development to enable this flow

p-himik 2024-02-26T15:07:23.004449Z

Ah, I see. 2-5 seconds sounds a bit much though. Quite a bit.

Jérémie Pelletier 2024-02-26T15:07:41.434799Z

yeah its over 1 minute before the JVM optimizations kick in haha

p-himik 2024-02-26T15:08:03.749489Z

...what? How do you hot reload your frontend code?

p-himik 2024-02-26T15:08:16.510609Z

Or did you mean the backend?

Jérémie Pelletier 2024-02-26T15:08:37.888509Z

ah I mean the cljs compiler running over the JVM, on a fresh start the initial compile is slow

Jérémie Pelletier 2024-02-26T15:09:01.252739Z

second one drops to 10 seconds, then its 2-5 secs, hotspot seems to be working great here

p-himik 2024-02-26T15:09:58.949029Z

Those timings are bizarre. You are not using shadow-cljs, are you?

Jérémie Pelletier 2024-02-26T15:10:04.399349Z

figwheel-main

p-himik 2024-02-26T15:10:24.458249Z

It might not be hotspot but rather some caching. Shadow-cljs has great approaches, timings are pretty much never that high.

Jérémie Pelletier 2024-02-26T15:10:42.957929Z

yeah its probably the extensive macro work im doing in most cljs namespaces, and reloads only doing partial compiles of the affected dependencies

p-himik 2024-02-26T15:11:00.294169Z

The initial start is around 15 seconds, all subsequent hot reloads (including the time to make the whole web page re-rendered) are less than a second.

Jérémie Pelletier 2024-02-26T15:11:09.529849Z

wouldnt that depend on project size?

Jérémie Pelletier 2024-02-26T15:11:42.341949Z

say 50+ cljs files, sometimes going over 100kb, and extensive macro transformations (ie clj -> wgsl+js with full data structures)

Jérémie Pelletier 2024-02-26T15:12:41.711189Z

persistent maps are great, but also orders of magnitude slower than mutations when chaining transforms

p-himik 2024-02-26T15:14:49.130969Z

Only the very first compilation would depend on the project size a lot, when there's no cache at all. Any subsequent compiler server launch is much faster because most of the stuff is cached. Any subsequent compilation and reloading of a changed file is even faster because the server is already running and even more things are cached. The only thing that can make it slow is if you change some ns that's a direct or transitive dependency of most other ns'es - then they all have to be reloaded. > say 50+ cljs files, sometimes going over 100kb The timings I provided above are for a project around 10 times larger. Much more than that if I include all of the dependencies that shadow-cljs also has to compile.

p-himik 2024-02-26T15:16:57.577889Z

> and extensive macro transformations (ie clj -> wgsl+js with full data structures) You should be able to cache those. > persistent maps are great, but also orders of magnitude slower than mutations when chaining transforms I don't know what it has to do with anything. Maybe you meant it as an explanation of why you're using wgsl+js, but I just have zero knowledge in the area.

Jérémie Pelletier 2024-02-26T15:18:18.616089Z

ah i mean most of the cljs code is driving a transpiler to generate WSGL shaders and the equivalent webgpu bindings, probably could do some caching right now it's re-evaluating the entire thing when a ns is recompiled