This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-08-02
Channels
- # announcements (3)
- # aws (2)
- # babashka (60)
- # beginners (21)
- # cljs-dev (35)
- # cljsrn (3)
- # clojure (53)
- # clojure-android (2)
- # clojure-australia (3)
- # clojure-europe (45)
- # clojure-france (4)
- # clojure-nl (4)
- # clojure-uk (6)
- # clojurescript (33)
- # core-typed (1)
- # cursive (13)
- # datomic (6)
- # duct (1)
- # emacs (2)
- # fulcro (10)
- # introduce-yourself (3)
- # jobs (2)
- # jobs-discuss (13)
- # leiningen (1)
- # malli (19)
- # missionary (63)
- # music (1)
- # off-topic (21)
- # pathom (3)
- # polylith (18)
- # practicalli (12)
- # proletarian (1)
- # reagent (40)
- # reitit (23)
- # releases (1)
- # remote-jobs (1)
- # ring (14)
- # ring-swagger (1)
- # shadow-cljs (13)
- # sql (30)
- # testing (27)
- # tools-deps (31)
- # vim (10)
- # xtdb (4)
Kinda getting started with Reagent and doing a lot of (::foo @state)
which as I understand it will rerun the function every time state changes, rather than every time ::foo changes. Is there like a straightforward "do this instead" or it's complicated and you have to architect your state into smaller bits? The solution probably involves cursors or reactions... Looking at https://github.com/reagent-project/reagent/blob/master/doc/ManagingState.md I should probably just make all the interesting pieces of my state atom a cursor, or just split up the state atom in smaller bits? Is there any advantage of doing the one or the other? The cursor one probably makes it easier to do atomic updates?
Can I just s/(:foo @state)/@(cursor state [:foo])/
or you need to define it at the top level?
@pepijndevos Yes, use cursors. Or alternatively, if this is in Re-frame, use subscriptions. And don’t deref the cursor directly as part of creating it (in your regex example above you are doing both at the same time). You can create it in a let (local binding) and then you can deref inside of a form-2 component’s render function. Then you will have properly separated the creation of the cursor (should happen only once) from the reactive deref (should happen every time the cursor’s value changes).
e.g. something like
(defn my-component []
(let [my-cursor (cursor state [:foo])]
(fn []
[:div @my-cursor])))
Huh.... the extra fn is just there to make sure the cursor is only evaluated once?
When you return a render function in reagent, (what I did) that’s a form-2 component. When you define a render function directly, that’s a form-1 component.
You can also do this as a form-1 component, but in that case you will need to def
your cursor somewhere inside the namespace. If this is a single-use component that is definitely also possible. Regardless, you should still learn about form-2 components 🙂
But so like uh... inside utility functions and event handlers and the like, you probably want to def the cursor I guess
No, you should never def anything inside a function. That is an antipattern. Defs should only be top-level. Use local bindings - i.e. let
- if you need to access something by name after it’s been created. If you need to access it globally, then yes, by all means, def
it inside the namespace.
Yea that's what I mean, def at the top level. If you let it inside an utility or event handler then it will be evaluated multiple times
Erased??? You mean the cursor keeps some cache I suppose?
Yea I'm basically wondering how bad @(cursor state [:foo])
is (adding a let doesn't change much in this case)
Yeah, if you calculate something in a let
inside a function and then keep calling that function, you will probably be redoing that calculation.
I don’t understand what you’re on about now. Obviously adding a let by itself doesn’t change much if you don’t make a form-2 component, but the point of a form-2 component is that the outer function is run only once, in which case you will only create the cursor once.
So don’t deref in the let
, do that in the render function that the component returns, i.e. what I did in my example. Otherwise, if your component isn’t meant to be reused at all, just def
your cursor inside the namespace and deref it from a form-1 component.
Merging cursor creation and deref’ing is indeed bad if the point of the cursor is achieving better performance, which is pretty much always is. Why bother with cursors if you’re not looking to get better performance?
> I don’t understand what you’re on about now. Obviously adding a let by itself doesn’t change much if you don’t make a form-2 component, but the point of a form-2 component is that the outer function is run only once, in which case you will only create the cursor once. it sounds like @pepijndevos is asking what the cost of:
(defn my-comp
(let [my-cursor @(cursor state [:foo])]
[:div @my-cursor]))
vs. using a form-2 componentit doesn't sound like they're confused how reagent cursors & form-2 components work on a fundamental level
obviously you're going to be reconstructing the cursor object every render here. the cost of that shouldn't be too high
it looks like, reading the source, that when you initially deref a cursor it creates a new reaction that does (get-in ratom path)
and caches it on the ratom itself
https://github.com/reagent-project/reagent/blob/master/src/reagent/ratom.cljs#L275-L278
cached-reaction
is quite a hairy function but it seems that in this case it sets a field on ratom
that contains a cache of reactions that depend on it, keyed by the path given to the cursor
the way i'm interpreting it is that repeated calls to (cursor state [:foo])
will reuse the cached reaction instead of constructing a new one
yeah since reagent detects usage of the same reaction across renders, I don't think it will dispose of the reaction
the old cursor object will get garbage collected, but that doesn't trigger disposal
Okay so conclusion is, it's best to use form-2+let or toplevel def, but in utility functions the overhead of creating a new cursor is reasonably small? Is there a good way to profile this kind of stuff? I can't say I've tried very hard but glancing at the Firefox profiler it was kind of opaque what was causing needless rendering.
I don't see it mentioned above, but to make some things simpler you can use reagent.core/with-let
to make your form-1 component behave pretty much like a form-2 component.